From 412310e71e8ba505012ca3a7fcf4e967041f4142 Mon Sep 17 00:00:00 2001 From: artpi Date: Tue, 8 Sep 2026 09:19:40 +0200 Subject: [PATCH 1/3] Reconcile contributor guidance with standalone PersonalOS plugins --- .cursor/rules/dev-environment.mdc | 25 +--- .cursor/rules/layout.mdc | 16 +-- .cursor/rules/update-agents-md.mdc | 25 +--- AGENTS.md | 170 +++++++++---------------- README.md | 64 ++++++---- docs/development.md | 74 +++++++++++ docs/knowledge-contract.md | 51 ++++++++ docs/packaging.md | 60 +++++++++ docs/sync-contracts.md | 37 ++++++ packages/personal-ai-chat/TECHNICAL.md | 20 +++ packages/personal-notes/TECHNICAL.md | 41 ++++++ packages/personal-stuff/TECHNICAL.md | 6 + packages/personal-todo/TECHNICAL.md | 6 + 13 files changed, 413 insertions(+), 182 deletions(-) create mode 100644 docs/development.md create mode 100644 docs/knowledge-contract.md create mode 100644 docs/packaging.md create mode 100644 docs/sync-contracts.md create mode 100644 packages/personal-ai-chat/TECHNICAL.md create mode 100644 packages/personal-notes/TECHNICAL.md diff --git a/.cursor/rules/dev-environment.mdc b/.cursor/rules/dev-environment.mdc index 1d0f017..8cf279e 100644 --- a/.cursor/rules/dev-environment.mdc +++ b/.cursor/rules/dev-environment.mdc @@ -1,20 +1,9 @@ --- -description: -globs: -alwaysApply: true +description: Local WordPress development, tests and environment repair +globs: .wp-env*.json,package.json,composer.json,tests/**,tools/** +alwaysApply: false --- -The dev environment is managed by the `wp-env` package. -In order to run code and run tests, you need to use the proper syntax to run commands. - -If you think the test environment failed DO NOT TRY TO FIX IT. Tell me. - -## Testing Commands - -- To run unit tests, you would `npm run test:unit:backend` -- To read the debug.log you would `npm run wp-env run cli -- tail -n 100 wp-content/debug.log` -- To run a wp-cli "wp pos" command you would do `npm run wp-env run cli wp pos` - -## Versioning - -- Version numbers must use three-part semantic versioning format (e.g., `0.3.0`, not `0.3`) -- Update version in both `personalos.php` (plugin header and `$version` property) and `package.json` +Use [Development](../../docs/development.md) for current commands, fixture +boundaries, focused verification and routine local environment repair. +Use [Packaging](../../docs/packaging.md) for package versions and deployment. +Do not substitute legacy monolith commands for the current split-package flow. diff --git a/.cursor/rules/layout.mdc b/.cursor/rules/layout.mdc index ad5d015..55b7bb2 100644 --- a/.cursor/rules/layout.mdc +++ b/.cursor/rules/layout.mdc @@ -1,13 +1,9 @@ --- -description: -globs: +description: PersonalOS repository layout and contributor guidance +globs: alwaysApply: true --- -This is a repository with a project called PersonalOS. -It is a WordPress plugin that turns WordPress into a personal productivity system. - -Here are the different directories: - -- `src-chatbot` is a directory with a NextJS app that DOES NOT conform to WordPress rules. DO NOT apply the wordpress formatting rules there. This app builds to the `build/chatbot` and is included in WordPress plugin distribution -- `src` are the JS components and Gutenberg blocks for the plugin -- `modules` are different modules for the plugin. Each functionality is wrapped in a module inheriting `class-pos-module.php` +Read [AGENTS.md](../../AGENTS.md) for the current architecture and task-specific +reference routing. Current plugins live in `packages/` with helpers in +`shared/php`. The root plugin, `modules/`, and `src-chatbot/` are legacy. +WordPress formatting does not apply to the separate Next.js app. diff --git a/.cursor/rules/update-agents-md.mdc b/.cursor/rules/update-agents-md.mdc index 0a29719..768f9fc 100644 --- a/.cursor/rules/update-agents-md.mdc +++ b/.cursor/rules/update-agents-md.mdc @@ -1,22 +1,9 @@ --- -description: Rule for updating AGENTS.md when new TECHNICAL.md files are created in modules -globs: modules/**/TECHNICAL.md +description: Maintain current, scoped contributor and technical guidance +globs: AGENTS.md,packages/**/TECHNICAL.md,modules/**/TECHNICAL.md,docs/*.md alwaysApply: false --- -# Updating AGENTS.md for TECHNICAL.md files - -Whenever a new TECHNICAL.md file is created in any module directory, you MUST: - -1. Update `AGENTS.md` to include the new TECHNICAL.md file in the "Current TECHNICAL.md files" section -2. List the file using the format: `- \`modules/[module-name]/TECHNICAL.md\` - Brief description of what the module does` - - -## Example - -If you create `modules/newmodule/TECHNICAL.md`, add this line to AGENTS.md: - -``` -- `modules/newmodule/TECHNICAL.md` - Technical documentation for the NewModule module -``` - -This ensures that all technical documentation is discoverable and documented in the main AGENTS.md file. +Follow [Maintaining instructions](../../AGENTS.md#maintaining-instructions). +Replace superseded rules instead of appending contradictory lessons. Put package +contracts beside their source, shared contracts in `docs/`, and link new current +contracts from AGENTS.md. Legacy module docs remain scoped to legacy work. diff --git a/AGENTS.md b/AGENTS.md index 781f75e..63f06a0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,107 +1,63 @@ -This is a WordPress plugin serving as Personal Operating System with todos, notes, ai integration etc. - -## Other files you should read - -- .cursor/rules/layout.mdc to learn about the layout of the repository, what dir does what -- .cursor/rules/dev-environment.mdc to learn about dev environment and testing environment -- .cursor/rules/wordpress-coding-standards/wordpress-coding-standards-php.mdc to learn about standards for php -- .cursor/rules/wordpress-coding-standards/wordpress-coding-standards-javascript.mdc to learn about standards for js. - -## Module Technical Documentation - -Modules can have TECHNICAL.md files that document their technical implementation details, architecture, and design decisions. These files are valuable for understanding how specific modules work. - -### Current TECHNICAL.md files - -- `modules/imap/TECHNICAL.md` - Technical documentation for the IMAP module -- `modules/openai/TECHNICAL.md` - Technical documentation for the OpenAI module -- `modules/slack/TECHNICAL.md` - Technical documentation for the Slack integration module -- `packages/personal-todo/TECHNICAL.md` - Canonical storage, lifecycle, API, and testing contract for Personal TODO - -## Coding - -Assume environment is set up. - -- `composer run lint -- "file/path"` to lint -- `composer run phpcbf -- "file/path"` to format entire file, including whitespace. Dont try to use python or other scripts to add whitespace. -- `npm run test:unit:backend` to run tests. Feel free to run them often. - -## Your behaviour - -- IMPORTANT: When you learn something about the codebase or how I want you to operate, add it to lessons below. - -### Lessons -- Personal Stuff should not create an empty Knowledge row merely when Add item is clicked; keep a new editor transient until Save, while allowing the first upload to persist the required authorized media parent. Its DataViews collection should open View as the primary action, progressively load on scroll, and use WordPress responsive image candidates rather than downloading full-size originals for thumbnails. -- Personal Stuff UX should keep search and uncropped photos prominent, minimize mobile header clutter, and use a full-viewport add/edit flow on phones with inputs at least 16px and persistent save/progress controls. Keep secondary photo controls and block settings optional. `docs/personal-stuff-ux-feedback.md` records applicable feedback mined from the original Stuff sessions; no PRs were available there. -- DataViews grid `layout.previewSize` is a column count, not a pixel width. Leave it unset for responsive defaults; a value such as 240 produces an unusable grid below the library's mobile breakpoint. -- On M5, offer the optional Tailscale hostname setup when working with wp-env WordPress projects, but never enable it automatically or treat this project's approval as consent for other projects. The shared machine-context skill and M5 profile hold the reusable procedure; localhost remains the default until the user accepts. -- Host-specific wp-env domains belong in ignored `.wp-env.override.json` under `env.development.config` (`WP_HOME` and `WP_SITEURL`), leaving the PHPUnit site's URL local. Back up and replace stored development URLs with serialization-aware WP-CLI search-replace, skipping GUIDs. Normal wp-env startup reactivates configured packages on both sites, so a standalone ZIP test copy must not remain active alongside the same source plugin when returning to the suite. -- Keep CSS lint dependencies compatible with the installed WordPress scripts: scripts 30.7.0 uses `@wordpress/stylelint-config/scss-stylistic` from config 23.6.0 and Stylelint 16.12.0. Declare the config and binary explicitly, preserve the lockfile, and validate with `npm ci`; ignore generated/bundled CSS and the separate Next.js project, while linting all WordPress source styles without disabling rules. -- Personal Stuff creates a private empty Knowledge row before opening its editor so every upload surface has a saved authorized parent. Use both upload and sideload prefilters for filename randomization: Gutenberg/core REST supports raw-body and multipart uploads, while the native Media Library uses verified `post_id`. Keep originals and derivatives on the same random basename. -- Browser tests confirmed Stuff and Personal Notes share native Gutenberg content without adding the `note` identity to Stuff items; Notes continues to list note records. Moving/tagging an item must omit unchanged `content` so gallery captions, order and custom blocks remain intact. -- Personal Stuff is planned as a standalone plugin replicating `~/GIT/stuff`; its physical places must be hierarchical `wp_knowledge_type` terms in a tree rooted at the top-level `stuff` Knowledge Type, not separate place posts or `post_parent` relationships. Items remain individual Knowledge posts, and location is a taxonomy assignment. -- Personal Stuff item photos and galleries belong in `post_content` as native Gutenberg image/gallery blocks, with block order determining photo order and cover. Both Gutenberg and the native Stuff UI must edit the same content losslessly; do not store a parallel gallery in meta. Include a required operating skill, but no importing affordances or importer phase. `docs/personal-stuff-plan.md` holds the revised lean plan. -- Personal Stuff has no quantity field and no linked place-content posts or `personal_stuff_content_id`. Places/tags are raw taxonomy terms using native name, slug, parent and description; omit place galleries and custom Stuff post/term meta. Photos belong to item posts only; existing Knowledge provenance and normal WordPress attachment metadata may remain. -- Personal Stuff is primarily a JS app over existing Knowledge, Knowledge Type and Media REST APIs. Add no custom REST namespace, server projection API, package-specific Abilities, storage schema, ACL registry or persistent lock/idempotency store. Keep PHP thin: fixed-term provisioning, WpApp/assets/editor integration, scoped upload filename randomization and narrow native hooks as needed; rely on existing WordPress permissions and editing behavior. -- Prefer the simplest Personal Stuff implementation: a small PHP bootstrap for terms and required integration, with JS using existing WordPress APIs/components. Do not prebuild controllers/services, custom permissions/concurrency, a separate editor engine, media reference scanner, repair framework or offline subsystem. Preserve the requested inventory UI using native facilities; add abstractions only when concrete implementation needs justify them. -- Personal Stuff should expose a private top-level `/stuff/` app through the existing bundled WpApp helper, with wp-admin entries acting as launchers and My Apps integration when available. The user meant WpApps, not a my.wordpress.net-specific browser runtime; do not add browser-local deployment requirements from that reference. -- Personal Stuff routing terms such as `stuff`, `stuff-places` and `stuff-tags` must be recognized by stable slugs and created idempotently on activation, with deferred provisioning when Knowledge becomes available. Do not store root term IDs, installed/schema flags or other speculative persistent plugin options; derive readiness from actual terms and use native WordPress relationships. -- Personal Stuff must replace new upload basenames with cryptographically random names before files enter WordPress's public Media uploads directory, preserving validated extensions and applying the rule in both the Stuff UI and its Gutenberg upload context. Original/scaled/thumbnail filenames must retain the unpredictable basename. These remain public URLs that are hard to guess, not private media; do not rename unrelated uploads or existing attachments retroactively, and do not add an option for this rule. -- When porting Stuff, inspect its current code as well as its PRD: the app includes filtered collection links, Print / PDF, contextual previous/next and swipe navigation, inline description editing, photo recovery/publication, and cached read-only browsing. Do not copy its broad same-origin service-worker caching into an authenticated WordPress app. -- For the plugin split, prefer a lean Personal-branded, Knowledge-centered target: Notes UI, Readwise sync, Evernote sync, TODO+ICS, and AI Chat; remove/defer OpenAI, Perplexity, IMAP/email responder, podcast/ElevenLabs, transcription, and voice/realtime instead of extracting every old module. -- TODO should store each task as its own `wp_knowledge` post tagged `artifact` and `todo` so non-PersonalOS Knowledge consumers can discover tasks; do not use `plan` or `personalos` for ordinary TODO rows. -- Current GitHub Actions build one monolithic `wp-personal-os.zip`; the plugin split needs a package matrix that builds and uploads separate ZIPs for Notes, Readwise, Evernote, TODO, and AI Chat. -- The current NPM build is monolithic (`src/index.js` -> `build/index.js`); split plugins need package-local JS/CSS builds, block registration, and sidebars so no package depends on the root `pos` editor bundle. -- Split packages should be WordPress.org-submittable immediately; use store-safe names/readmes/headers/licenses/privacy disclosures, avoid `Requires Plugins` between PersonalOS packages, and bundle shared helper classes into each ZIP with `class_exists` guards instead of creating a required base plugin. -- For split plugin naming, use Personal-branded WordPress.org display names like `Personal Notes`, `Personal TODO`, `Personal AI Chat`, `Personal Readwise Sync`, and `Personal Evernote Sync` while keeping in-product labels short like `Notes`, `TODO`, and `AI Chat`; use sync package slugs such as `personal-readwise-sync` and `personal-evernote-sync`, not importer names. -- For the plugin split, do not spend effort on backward compatibility or old PersonalOS data migration; ignore old `notes`/`todo` CPT rows, prompt notebooks, AI memory/chat notes, and old provider settings. -- Recreate the old PARA-inspired organization with shared `wp_knowledge_type` terms such as `inbox`, `now`, `project`, `area`, and `resource`; do not add a separate PersonalOS-only collection taxonomy for v1. -- The resolved Knowledge type taxonomy is hierarchical for the split; normalize existing flat terms by reparenting them with stable slugs/term IDs, and explicitly expand parent terms to child IDs for parent-level filters. -- PARA/Knowledge type terms should keep stable semantic slugs but preserve the old numbered top-level display labels (`1-Status`, `2-Projects`, `3-Areas`, `4-Resources`) so WordPress core taxonomy fields sort naturally; code must resolve by slug/term ID, not label text. -- PARA container terms such as `status`, `project`, `area`, `resource`, and `archive` are for hierarchy and filter expansion only; never apply them directly to Knowledge posts, only assign their child terms. -- Keep shared Knowledge type vocabulary/normalization/query expansion in bundled helper code used by every split plugin; Notes owns the full PARA management UI, while TODO owns only its task-facing assignment/filter UI. -- Refer to the shared data/runtime concept as Knowledge in docs, code names, and helper class names. Shared bridge/comms code requires `wp_knowledge` and `wp_knowledge_type`, uses `knowledge_source` when registered, and must not carry Guidelines-era aliases. -- For split shared code, create bundled base classes like `PersonalOS_Plugin_Base` and `PersonalOS_Sync_Plugin_Base` that reuse the good `POS_Module` ideas (settings, CLI, logging, sync hooks, package assets, Knowledge bridge access) without carrying over the global module registry, sibling injection, root build paths, or a required base plugin. -- When porting PersonalOS modules to Knowledge, keep the old storage architecture close: preserve meaningful setting IDs and meta keys such as `readwise_id`, `evernote_guid`, `pos_blocked_by`, `pos_recurring_days`, `pos_model`, and `pos_last_response_id`, but register them through `PersonalOS_Knowledge_Bridge` on the Knowledge post type and type taxonomy, and use package-scoped option prefixes like `personal_readwise_sync_*`. -- For split user scoping, carry forward PR #72's useful ideas but rely on Knowledge row permissions: user artifacts are private, user-authored Knowledge rows; site artifacts are admin-created published Knowledge; user credentials/cursors live in user meta; site settings live in options; do not port `notes_user` or replacement sync-user options. -- For the split wp-env setup, target WordPress 7.0 core surfaces and do not mount the standalone `WordPress/abilities-api` plugin by default; only use it in an explicit legacy/pre-7.0 compatibility config. -- For Artpi's preferred WordPress PHP style, follow the global `artpi-wp-php-style` skill: use module-local, item-shaped config arrays and WordPress-native storage/hooks over parallel constant lists, custom registries, or abstraction-heavy refactors; keep pragmatic WPCS exceptions and behavior-first tests. -- The WordPress PHP style skill now reflects Codex history across WordPress-touching repos, not only PersonalOS: keep WordPress as source of truth, preserve native CPT/block/template formats, accept stable slugs where useful, keep permission checks close to REST/content operations, expose repeatable WP-CLI/wp-env workflows, and prefer behavior-preserving cleanup over broad rewrites. -- Artpi dislikes unnecessary wrapper methods; create helper methods only when they carry domain meaning, centralize a real rule, reduce meaningful duplication, or are meant to be overridden. Prefer OOP polymorphism for real behavior changes, like the `WPCOM\AI\Message`, `Tool_Call`, and `Tool_Call_Result` family in `~/GIT/wpcom`. -- Claude Code history was mined for the style skill too: treat Abilities API abilities as permission/trust surfaces, use constrained action enums for same-risk multi-action abilities, gate enablement/execution on configured settings while keeping registration available, prefer PHP/WordPress APIs over shelling out, and keep local secrets/sync/deploy helpers out of git. -- Keep the global `artpi-wp-php-style` skill reusable across WordPress repos: avoid single-project class/module examples, and when saving meta/options/data, store less if a value can be inferred from canonical saved data unless caching, queryability, or performance makes the duplicate worthwhile. -- External history analysis added a few reusable style rules: prefer focused debugger/CLI/log traces before AI evals or raw DB/shell poking, move behavior tests during migrations while allowing small canary tests, preserve old loaders/contracts for rolling deploy safety, and do not mutate shared queue/job tables directly when app-owned state can express cancellation/status. -- Split package source may load shared helpers from the monorepo `shared/php` directory for local development, but release ZIPs must bundle those helpers under each package's `includes/shared/` directory via the package build script so installed plugins do not depend on the repo layout. -- Split package REST APIs should live in package namespaces such as `personal-notes/v1` and `personal-todo/v1` while reading/writing only resolved Knowledge rows; Notes updates should preserve provenance/source terms like `manual`, `readwise`, `evernote`, and `synced`, and TODO completion should keep using the trash workflow so recurrence and unblock side effects still run. -- Split package block/editor assets may be compiled through the root webpack config as an intermediate, but `npm run build` must sync each package's assets into `packages//build`, and ZIP verification must check package-local block `block.json`, `index.js`, and `index.asset.php` files so wp-env and installed packages never depend on root `build/` paths. -- TODO history in the split belongs in `todo_note` comments attached to the task Knowledge post; package REST/ability formatters may expose recent history, but do not duplicate that activity trail into task meta or package-private storage. -- TODO Abilities API callbacks in the split should cover list/create/update/complete and reuse the package REST mutation semantics after task/capability checks, so agents and REST clients get the same trash, history, meta, term, recurrence, and unblock behavior. -- AI Chat generation in the split should stay server-side and provider-agnostic: expose specific REST endpoints that call `wp_ai_client_prompt()->generate_text()` when available, keep provider credentials in Connectors/AI Client, and save user/assistant turns back into conversation Knowledge as `pos/ai-message` blocks. -- Evernote sync in the split should keep transport separate from package ownership: the package owns active-user iteration, current-user switching, notebook/tag scope checks, per-user cursor/cache settings, and idempotent Knowledge upserts by `evernote_guid`; any SDK/client transport can feed normalized notes into that loop. -- Split package verification should be treated as a store-readiness gate, not only a file-existence check: require privacy and data-retention readme sections, reject placeholder/Requires Plugins text, scan source for monolith/sibling runtime coupling and remote executable admin scripts, and inspect ZIP contents for excluded files and bundled shared helpers. -- Readwise `pos/book-summary` belongs in Personal Readwise Sync, but generation must go through a package REST endpoint backed by WordPress AI Client/Connectors; do not depend on old `/pos/v1/openai/*` routes or package-local provider credentials. -- Evernote split sync should keep a package-owned one-way import transport: load the bundled Evernote SDK runtime when present, normalize matching sync-chunk notes into Knowledge, and bundle only the SDK/PSR runtime files needed by the standalone ZIP rather than Composer's dev autoload or the old two-way Notes module. -- Split package PHP strings should use each package's WordPress.org text domain (`personal-notes`, `personal-todo`, `personal-ai-chat`, etc.); because the root PHPCS config still expects `personalos`, add a narrow `WordPress.WP.I18n.TextDomainMismatch` exception in package PHP files instead of reverting to the monolith domain. -- Only destination-style split packages are private WpApps: Notes at `/notes/`, TODO at `/todo/`, and AI Chat at `/ai-chat/`. Readwise and Evernote are background integrations with wp-admin settings screens, not My Apps entries. Bundle pinned WpApp 1.3.2 only in the three app ZIPs and require PHP 7.4 only there; the sync packages remain on PHP 7.2.24. -- Personal Notes owns a package-local Gutenberg sidebar for post block editors. It must search the core Knowledge REST collection, filter by Knowledge Type, preview notes, and insert or drag `pos/note` blocks; block-shaped Knowledge notes receive it naturally, classic Markdown notes do not load block-editor assets, and old monolith Readwise/Evernote document panels must not be ported. -- In the Personal Notes Gutenberg editor, expose WordPress's native raw `wp_knowledge_type` taxonomy panel. The user accepts that this makes internal, system, and container terms directly assignable; do not replace it with a restricted custom term picker. -- Use WordPress DataViews for the split Notes library, backed by the canonical Knowledge CPT and taxonomy REST endpoints, and hand note creation/editing off to the native Knowledge block editor; do not recreate a package-private note editor or duplicate core collection behavior. When the Knowledge runtime sets `show_ui` false, Notes may enable its native editor while leaving the full Knowledge list out of the admin menu. Keep TODO's app list aligned with the same DataViews interaction model. -- wp-env mounts split package directories independently under `wp-content/plugins`, so split-suite and single-package configs must map monorepo `shared/php` and `vendor` into `wp-content/shared/php` and `wp-content/vendor`; otherwise package fallback loaders cannot find shared base classes or local Composer runtimes during activation. -- The current wp-env WordPress image does not register Knowledge itself. Split-suite and single-package configs use pinned Gutenberg 23.7.0 as a development-only fixture and enable its `gutenberg-guidelines` experiment after startup so `wp_knowledge` is available for functional testing; this is not a release dependency. -- Preserve the established PersonalOS TODO workflow in the split app: compact quick-add, DataViews list/filter/actions, full edit modal, scheduling, recurrence, dependencies, and history. Adapt old notebook controls to shared Knowledge type terms instead of replacing the UI with a simplified custom task list. -- Destination app wp-admin menu entries are launchers, not alternate app screens: Notes, TODO, and AI Chat should redirect to their private WpApp routes, while Readwise and Evernote remain conventional wp-admin Settings pages. -- Shared frontend CSS imports used by multiple split packages must remain package-distinct during webpack extraction (for example with package-specific resource queries), so a standalone package never depends on a sibling plugin's generated style chunk. -- Split packages with durable domain records should include a package-local `TECHNICAL.md` that defines the exact WordPress representation, stored-versus-derived fields, lifecycle invariants, API shape, permissions, and regression-test contract; do not leave the canonical schema scattered between a migration plan and implementation code. -- `create_knowledge_post()` assigns Knowledge terms after `wp_insert_post()`, so post-save hooks that identify a row by terms cannot see its subtype during insertion; creation flows with term-dependent side effects, such as TODO recurrence scheduling, must run those side effects after term assignment and have a regression test for the resulting cron event. -- Personal TODO must provision its own identity and status vocabulary (`artifact`, `todo`, `inbox`, `now`, `later`, `follow-up`) so standalone scheduling and dependency transitions never target missing terms. Record completion history on `trashed_post`, not `wp_trash_post`, because WordPress marks all existing post comments `post-trashed` between those hooks. -- Mount the monorepo test source outside `wp-content/plugins` (currently `wp-content/personalos-tests`) and run PHPUnit from that path; mounting the root as a discoverable plugin can reactivate the old monolith in split wp-env and produce duplicate block registrations. -- Use `Knowledge Type` as the canonical user-facing name for the shared Knowledge taxonomy in Notes and TODO; do not relabel the same terms as collections. Internal plural names such as `knowledgeTypes` are appropriate for multi-value fields. -- Personal Notes chooses the native editor per resolved Knowledge row from `post_content`: use Gutenberg only when `has_blocks()` detects serialized block markup; use the classic editor for Markdown, plain text, classic HTML, and empty/new rows. Do not infer or persist a separate content-format flag. -- The split runtime contract is Knowledge-only: require `wp_knowledge`, `wp_knowledge_type`, `/wp/v2/knowledge`, and Knowledge-era hooks/meta when available. Do not retain `wp_guideline` post type, taxonomy, REST, label-hook, or source-meta fallbacks in shared helpers, packages, tests, or documentation. -- Pressidian enrolls previously unmanaged private Knowledge rows when they have any administrator-selected Knowledge Type (default `note` or `todo`), skips Gutenberg block-shaped content, and keeps already-enrolled rows synced if their types later change. It uses the existing immutable WordPress slug as record identity, calls the Markdown representation kind `markdown`, hides derived `artifact`/`note`/`obsidian` terms from Obsidian tags, exposes `todo` and other semantic terms as tags, preserves TODO operational meta, and treats WordPress trash as a sync tombstone rather than a hard delete. -- The default Knowledge-only wp-env intentionally omits the standalone Abilities API plugin. PHPUnit bootstrap must treat that fixture as optional and stay silent when it is absent; writing the expected absence to STDERR breaks tests marked `@runInSeparateProcess` before they can skip. -- `pull_request_target` evaluates the workflow definition from the base branch but may execute local composite actions from the checked-out PR merge ref. During CI migrations, keep changed composite actions backward-compatible with the base workflow's inputs until the workflow change has merged; the split build action therefore falls back to the legacy monolith preview ZIP when no package slug is supplied. -- The public landing page is the static GitHub Pages site in `docs/` (custom domain `personalos.net`). Keep it focused on the six standalone Knowledge-backed packages, distinguish four app destinations from two background sync integrations, and verify release assets before advertising downloads; the merged Stuff package initially exists only in source. Use synthetic demo records for public app screenshots and attach a full-page design capture to landing-page PRs. -- Private site deployment helpers live in gitignored `tools/local/`; inspect that directory when asked to deploy. Run them only for an explicitly requested deployment, and keep host details, backups, and credentials out of tracked files. -- Deploying the whole current PersonalOS suite means building and installing all six standalone packages under `packages/`, including Personal Stuff, with their bundled shared helpers and WpApp assets. The root `personalos.php` is the legacy monolith, does not load the split packages, and must remain inactive alongside the current suite. -- Split-suite live verification must exercise creation flows, not just activation and route presence: Notes needs all identity/source terms before its core REST create flow, and AI Chat must normalize the actual abilities response before array operations. Activation/package verification alone missed both failures on a fresh site. +# PersonalOS contributor guidance + +PersonalOS is a monorepo of six independent WordPress plugins built on Knowledge. +The current code lives in `packages/`, with bundled helpers in `shared/php`. +The root `personalos.php`, `modules/`, and `src-chatbot/` belong to the legacy +monolith; do not activate it alongside the split suite. + +## Read for the task + +Read the relevant contract before changing its behavior; do not load every +reference for an unrelated edit. + +| Work area | Guidance | +| --- | --- | +| Shared Knowledge storage, terms, permissions, interoperability | [Knowledge contract](docs/knowledge-contract.md) | +| Local setup, tests, lint, environment repair | [Development](docs/development.md) | +| ZIPs, CI, releases, deployment | [Packaging](docs/packaging.md) | +| Stuff UI, media, taxonomy, operating API | [Stuff contract](packages/personal-stuff/TECHNICAL.md) and [operating skill](packages/personal-stuff/skills/personal-stuff/SKILL.md) | +| TODO storage, recurrence, history, ICS, abilities | [TODO contract](packages/personal-todo/TECHNICAL.md) | +| Notes library and native editors | [Notes contract](packages/personal-notes/TECHNICAL.md) | +| AI Chat | [AI Chat contract](packages/personal-ai-chat/TECHNICAL.md) | +| Readwise and Evernote | [Sync contracts](docs/sync-contracts.md) | +| Public landing page | Static site in `docs/`; use synthetic screenshot data, distinguish four apps from two sync integrations, and verify release assets before advertising downloads. Attach a full-page screenshot to landing-page PRs. | + +`docs/*-plan.md` files record design history; use current contracts and source +for implementation. Read legacy module documentation only for legacy work or +an explicit behavior comparison. + +## Working agreements + +- Preserve unrelated tracked and untracked work. If a requested fresh branch + would disturb it, use a separate worktree from the requested base. +- Prefer native WordPress APIs, storage and components. Add helpers only for a + meaningful domain rule, useful reuse, or an intentional override point. + Avoid speculative registries, migration layers and broad style refactors. +- Apply the repo's PHP/JS standards when changing that language; see + `.cursor/rules/wordpress-coding-standards/`. The optional personal + `artpi-wp-php-style` skill supplements the repo's actual lint configuration. + Do not impose WordPress formatting on the separate legacy Next.js app. +- Match validation to the changed behavior. UI work needs a browser check of + the interaction and resulting state; activation or a build alone is not UI + verification. See the development guide for commands and test boundaries. +- Carry authorization through the requested deliverable: a request to create + or update a PR includes scoped commits and pushes. A review-only request does + not. Report the branch, push status, PR URL, validation and remaining blockers. + Do not infer deployment or merging from a request to open a PR. +- Private deployment helpers and artifacts belong in ignored `tools/local/`. + Inspect them only when needed for an explicitly requested deployment; keep + host details, backups and credentials out of tracked documentation. + +## Maintaining instructions + +Update the existing rule when a decision changes and remove the superseded +rule. Add only durable guidance not already documented. Put package behavior +in its technical contract, shared behavior in the shared contracts, and session +progress or unresolved bugs in the issue/PR rather than a growing Lessons list. +For durable domain records, document stored versus derived fields, lifecycle, +API shape, permissions and regression checks in the package contract. Update the +routing table when adding a contract. Distinguish required behavior +from verified implementation; do not document an unfixed bug as already fixed. + +`CLAUDE.md` points to this file. Repo skills live in `.agents/skills`, with +`.claude/skills` as the compatibility link; keep third-party skill bodies intact. diff --git a/README.md b/README.md index ed5c8f2..b7180a7 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,36 @@ -# Personal OS is the operating system for your life installed in your digital home. - -A modern note-taking and productivity system built on WordPress with the latest and greatest WordPress features. - -### [🚀 Open PersonalOS Playground](https://playground.wordpress.net/?networking=yes#{%22steps%22:[{%22step%22:%22installPlugin%22,%22pluginData%22:{%22resource%22:%22url%22,%22url%22:%22https://github.com/artpi/personalos/releases/latest/download/wp-personal-os.zip%22}}],%22landingPage%22:%22/wp-admin/admin.php?page=personalos-settings%22,%22login%22:true}) - - -### Few principles - -- Methodologies borrowed from Building a Second Brain, and GTD -- Inspired by Roam Research, Logseq, Obsidian and Tana -- Opinionated. I will ship features because I want them -- Has to work with any WordPress supporting plugins -- Implement things the WordPress Way™. WordPress has a ton of new APIs. - -## Features - -I consider this a humble start. I intend to grow this feature list until it becomes my main notetaking app. - -- [TODOs](modules/todo/README.md) - Manage your TODOs right in WordPress or through a dedicated mobile app -- [Notes](modules/notes/README.md) - Fully private "note" CPT, with embeddable nesting - - [Readwise](modules/readwise/README.md) - Sync notes with Readwise - - [Evernote](modules/evernote/README.md) - Sync notes with Evernote via 2-way sync - - [Transcriptions](modules/openai/README.md) - Upload an mp3 and it will get transcribed and turned into a note. - -## How to install - -[Install](./docs/INSTALL.md) \ No newline at end of file +# PersonalOS + +PersonalOS is a suite of independent WordPress plugins for notes, tasks, chat, +sync and a home inventory, sharing WordPress Knowledge as their data model. + +| Plugin | Purpose | +| --- | --- | +| [Personal Notes](packages/personal-notes/readme.txt) | Notes library and native editing at `/notes/` | +| [Personal TODO](packages/personal-todo/readme.txt) | Tasks, recurrence, dependencies and ICS at `/todo/` | +| [Personal AI Chat](packages/personal-ai-chat/readme.txt) | AI Client/Connectors-backed conversations at `/ai-chat/` | +| [Personal Stuff](packages/personal-stuff/readme.txt) | Inventory, places and photos at `/stuff/` | +| [Personal Readwise Sync](packages/personal-readwise-sync/readme.txt) | Readwise import and summaries | +| [Personal Evernote Sync](packages/personal-evernote-sync/readme.txt) | One-way Evernote import | + +## Development and installation + +Start with [contributor guidance](AGENTS.md), [local development](docs/development.md) +and [packaging](docs/packaging.md). Build standalone ZIPs with +`npm run build` followed by `npm run package:plugins`; install the selected ZIPs +on a WordPress site providing the required Knowledge runtime. Package readmes +specify their requirements. The default wp-env config supplies a development +Knowledge fixture for all six packages. + +[GitHub releases](https://github.com/artpi/PersonalOS/releases) contain published +assets; verify that the desired package ZIP is present. A source merge does not +by itself publish a release. The root `personalos.php` and `modules/` are the +legacy monolith, not the current suite, and must not be active alongside it. + +## Principles + +- Borrow from Building a Second Brain and GTD. +- Implement features using native WordPress storage, APIs and components. +- Keep each package independently installable while preserving interoperability. + +The public landing page lives in `docs/`. Historical module documentation and +split plans remain available for reference, not as current installation guidance. diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..4e06a4c --- /dev/null +++ b/docs/development.md @@ -0,0 +1,74 @@ +# Development and verification + +Commands run from the repository root. `package.json`, `composer.json`, and the +selected `.wp-env*.json` are the source for exact commands and fixture versions. +Inspect the existing runtime before installing dependencies or restarting it. + +```sh +npm ci +composer install +npm run build +npm run wp-env -- start +``` + +The default wp-env starts the six split packages. Single-package commands such +as `npm run wp-env:stuff` select the corresponding fixture. Knowledge is provided +by pinned Gutenberg with its experiment enabled after startup; this fixture is +not a release dependency. The default omits the standalone Abilities API plugin; +use the explicit legacy config only for that compatibility work. + +## Focused checks + +| Change | Relevant checks | +| --- | --- | +| PHP | `composer run lint -- "path/to/file.php"`; `composer run phpcbf -- "path/to/file.php"` for formatting | +| WordPress JS/CSS | `npm run lint:js`, `npm run lint:css`, and affected behavior tests | +| Backend behavior | `npm run test:unit:backend`; `npm run test:integration:backend` when integration behavior changes | +| Stuff content/search | `npm run test:unit:stuff` | +| Assets | `npm run build`, then inspect the affected browser screen | +| Packaging/shared loaders | [Packaging checks](packaging.md) | +| Documentation only | Check links, commands against scripts/configs, and `git diff --check`; do not start WordPress solely for prose edits | + +Run focused tests first and broaden for shared behavior touched by the change. +Do not repeat successful suites without a new change or unresolved concern. +Use narrow PHPCS exceptions where the root `personalos` text-domain rule +conflicts with the package's own WordPress.org domain; never rename package +strings back to the monolith domain. Keep WordPress scripts, Stylelint and its +config compatible using `package.json` and the lockfile; do not disable rules to +hide dependency mismatches or lint generated/vendor CSS. + +## Runtime boundaries and repair + +Diagnose failures using command output, the selected fixture and logs. Routine +local dependency/build/config repairs needed for authorized implementation are +allowed. Preserve unrelated services, user data and local overrides; ask only +when the repair needs destructive resets, expands scope, or changes a shared or +production environment. Report the actual blocker and continue independent work. + +wp-env mounts `shared/php` and `vendor` under `wp-content` for split loaders. +Mount the root test source as `wp-content/personalos-tests`, outside plugins, +so startup cannot reactivate the legacy monolith. The test command runs there. +An absent optional Abilities fixture must not write STDERR during PHPUnit +bootstrap, which breaks isolated tests before they can skip. + +Read logs through `npm run wp-env -- run cli -- tail -n 100 wp-content/debug.log`. +Use package-owned WP-CLI commands when registered; do not assume legacy `wp pos` +is available in the split suite. + +Host-specific domains belong in ignored `.wp-env.override.json` under +`env.development.config` (`WP_HOME`, `WP_SITEURL`); leave test URLs local. Use +backups and serialization-aware WP-CLI search-replace, skipping GUIDs, for an +authorized domain change. On M5, offer the shared machine-context skill's optional +Tailscale setup; localhost remains the default without project-specific approval. +Normal startup reactivates configured packages: do not leave standalone ZIP test +copies active alongside the same source plugins. + +## Browser acceptance + +Exercise actual creation and editing, including relevant mobile layouts, API +responses and console errors. For the suite, check Notes creation with required +identity/source terms, TODO creation/completion, AI Chat creation and the actual +abilities response shape, and Stuff save/upload. Activation and route presence +previously missed Notes creation and AI Chat failures; do not claim those are +fixed without reproducing and verifying them. Follow package contracts for the +rest of the affected flows and report anything not exercised. diff --git a/docs/knowledge-contract.md b/docs/knowledge-contract.md new file mode 100644 index 0000000..16b9aba --- /dev/null +++ b/docs/knowledge-contract.md @@ -0,0 +1,51 @@ +# Shared Knowledge contract + +The split runtime requires `wp_knowledge`, `wp_knowledge_type`, and +`/wp/v2/knowledge`. Use `PersonalOS_Knowledge_Bridge` and the shared vocabulary +in `shared/php`; do not restore Guidelines-era aliases, old CPTs, or a required +base plugin. Each release bundles its shared helpers independently. + +## Storage and ownership + +Use native post fields, registered meta, taxonomy, options and user meta. +Derive values when inexpensive; do not add a parallel storage layer. Preserve +meaningful integration keys such as `readwise_id`, `evernote_guid`, +`pos_blocked_by`, `pos_recurring_days`, `pos_model` and `pos_last_response_id`. +Register package-owned metadata through the Knowledge bridge and scope options +by package. Use `knowledge_source` when registered and the bridge's documented +fallback otherwise. + +User artifacts are private, user-authored Knowledge rows. Site artifacts are +admin-created published rows. User credentials and cursors live in user meta; +site settings live in options. Permissions follow the actual Knowledge row and +operation, not a replacement sync-user registry. Do not migrate legacy `notes` +or `todo` CPT data or old monolith settings unless explicitly requested. + +## Knowledge Type vocabulary + +Use the name **Knowledge Type** in Notes and TODO UI. Resolve terms by stable +slugs/IDs rather than display labels. Preserve numbered PARA container labels +(`1-Status`, `2-Projects`, `3-Areas`, `4-Resources`) and normalize hierarchy +without replacing existing term identities. Parent filtering expands descendants. +Keep normalization and query expansion in the shared vocabulary. + +Container assignment is interface-specific: TODO's assignment controls exclude +structural roots; the native Notes/Gutenberg taxonomy panel intentionally allows +raw terms, including system/container terms. Stuff's app assigns leaf terms but +also preserves unrelated terms. Do not impose a global prohibition that changes +the native editor. See each package contract for its identity and assignment rules. + +## Integration boundaries + +Shared base classes own settings, assets, logging and Knowledge access without a +monolith module registry or sibling injection. Package-specific REST namespaces +are appropriate where the package needs them; Stuff deliberately uses core REST +only. Preserve native Gutenberg content and provenance when editing another +consumer's records. Term assignment happens after `wp_insert_post()` in the +shared create helper: term-dependent side effects must run after assignment. + +Pressidian is an independent consumer. Preserve immutable WordPress slugs, +Knowledge types, content and TODO operational metadata; trash is a lifecycle +signal, not an invitation to hard-delete. Consult Pressidian's current sync +contract for enrollment and Markdown/Gutenberg conversion rather than freezing +its evolving client rules in this repository. diff --git a/docs/packaging.md b/docs/packaging.md new file mode 100644 index 0000000..c09eecb --- /dev/null +++ b/docs/packaging.md @@ -0,0 +1,60 @@ +# Packaging, releases and deployment + +The current suite contains six independent plugins. Four expose private WpApps: +Notes (`/notes/`), TODO (`/todo/`), AI Chat (`/ai-chat/`), and Stuff (`/stuff/`). +Readwise and Evernote are background sync integrations with Settings pages. +App wp-admin entries are launchers, not duplicate app screens. + +## Build and verify + +```sh +npm run build +npm run package:plugins +npm run verify:split-packages +``` + +For one package: + +```sh +npm run package:plugin -- --package=personal-stuff +npm run verify:split-packages -- --package=personal-stuff +``` + +The root WordPress scripts build is followed by `tools/sync-package-builds.mjs`. +Installed packages must use their own `build/` JS, CSS, block metadata and asset +manifests, not the root build or a sibling plugin's chunks. Shared CSS imports +must remain package-distinct during extraction. + +`tools/package-plugin.mjs` bundles shared PHP under `includes/shared/`, WpApp +for the four app packages, and the minimal Evernote/PSR transport runtime for +Evernote. Use package headers and the build scripts for dependency/PHP versions, +not copied version lists. Each ZIP must activate without a sibling plugin or +the root monolith. Guard bundled shared classes against redeclaration. + +Inspect ZIP contents and run `verify:split-packages`: check excluded files, +package-local assets/helpers, privacy and data-retention sections, proper +WordPress.org names/domains/licenses, and no placeholder readme or sibling +runtime dependency. Do not introduce `Requires Plugins` between suite packages +or remote executable admin scripts. These checks support store readiness but +are not evidence of store approval or of working UI flows. + +## CI and releases + +`.github/workflows/release.yml` builds a six-package matrix and uploads each +`.zip`. Update the relevant package header/readme versions using +three-part semver; do not change only the root legacy version. + +The shared build action still supports a legacy preview fallback when no package +slug is passed. `pull_request_target` uses the base workflow while local actions +may come from the PR merge ref, so retain compatibility with base-workflow inputs +until a CI migration lands. This fallback is not the current suite deployment. +Verify available release assets before documenting downloads; a source merge +alone does not publish a package. + +## Deployment + +Deploy only when requested. The whole suite means all six independently built +packages; root `personalos.php` is the legacy monolith and must remain inactive. +Private helpers live in ignored `tools/local/`. Keep hosts, credentials and +backups there, not in tracked instructions. Verify installed package assets and +then exercise the creation flows in [Development](development.md#browser-acceptance). diff --git a/docs/sync-contracts.md b/docs/sync-contracts.md new file mode 100644 index 0000000..da29efb --- /dev/null +++ b/docs/sync-contracts.md @@ -0,0 +1,37 @@ +# Readwise and Evernote sync contracts + +These are independent background integrations with wp-admin Settings screens, +not WpApp destinations. Use the [Knowledge contract](knowledge-contract.md) for +storage and ownership and [Packaging](packaging.md) for bundled dependencies. + +## Shared boundaries + +The thin shared sync base owns reusable settings/auth/request facilities; each +package owns iteration, permissions, normalization, cursors and behavior. Preserve +provider IDs and user-scoped credentials/cursors. Transport feeds normalized notes +into idempotent Knowledge upserts rather than depending on the monolith registry, +Notes module or another package. Package source is under `packages/`. + +## Evernote + +Personal Evernote Sync is a one-way import transport. It owns active-user +iteration, current-user switching, notebook/tag scope checks, per-user cursor/cache +settings and idempotent upserts by `evernote_guid`. Load the bundled Evernote SDK +when present and normalize matching sync-chunk notes into Knowledge. Bundle only +needed SDK/PSR runtime files, not Composer's development autoload or the legacy +two-way Notes module. The old module README is not the split sync contract. + +## Readwise + +Personal Readwise Sync owns `pos/book-summary`. Generate summaries through a +package REST endpoint backed by WordPress AI Client/Connectors, without the old +`/pos/v1/openai/*` routes or package-local provider credentials. Preserve Readwise +integration IDs and source terms on updates. + +## Verification + +For sync changes, test user isolation, scope filters, repeat-run upserts, +provenance retention and cursor behavior after partial failure. For Readwise +summary changes, verify configured/unconfigured generation and persisted output. +Use fixtures or the specifically authorized account; packaging checks alone do +not verify provider access or synchronization behavior. diff --git a/packages/personal-ai-chat/TECHNICAL.md b/packages/personal-ai-chat/TECHNICAL.md new file mode 100644 index 0000000..8043e29 --- /dev/null +++ b/packages/personal-ai-chat/TECHNICAL.md @@ -0,0 +1,20 @@ +# Personal AI Chat contract + +AI Chat is an independent private WpApp at `/ai-chat/`. Store conversations as +Knowledge and use the [shared contract](../../docs/knowledge-contract.md) for +ownership, metadata and vocabulary. + +Generation remains server-side and provider-agnostic through package REST +endpoints and WordPress AI Client/Connectors. Keep provider credentials in +Connectors rather than package-local credentials. Save user/assistant turns in +conversation Knowledge as `pos/ai-message` blocks; preserve meaningful model and +response metadata (`pos_model`, `pos_last_response_id`). + +Use the package's PHP class and block metadata for exact REST and block schemas. +Do not restore old monolith `/pos/v1/openai/*` transport or root editor assets. + +Verification must exercise conversation creation and a response through the real +UI, persistence, permissions and configured/unconfigured provider states. Inspect +the actual abilities response before array operations; a response-shape mismatch +previously crashed creation. Activation/route registration does not prove this +flow works, and this contract does not claim the observed bug is fixed. diff --git a/packages/personal-notes/TECHNICAL.md b/packages/personal-notes/TECHNICAL.md new file mode 100644 index 0000000..9ac6598 --- /dev/null +++ b/packages/personal-notes/TECHNICAL.md @@ -0,0 +1,41 @@ +# Personal Notes contract + +Notes is an independent private WpApp at `/notes/`, backed by shared Knowledge. +See the [shared contract](../../docs/knowledge-contract.md) for ownership and +vocabulary and the package PHP/JS for exact routes and fields. + +## Library and editors + +Use WordPress DataViews and canonical Knowledge REST collections. Hand editing +to the native Knowledge editor instead of adding a private editor engine. Notes +may enable that editor when Knowledge has `show_ui` false while keeping the full +Knowledge list out of the admin menu. + +Choose Gutenberg only when `has_blocks(post_content)` detects serialized blocks; +use the classic editor for Markdown, plain text, classic HTML and empty/new rows. +Do not persist a parallel format flag. Stuff's later editor filter explicitly +enables Gutenberg for its items, including empty ones. + +Expose the raw native `wp_knowledge_type` panel. System and container terms are +intentionally assignable there; do not replace it with TODO's restricted picker. +Use Knowledge Type as the label. Notes owns the full PARA management UI, while +shared helpers own vocabulary normalization and query expansion. + +The library lists `note` records. Stuff uses `artifact` + `stuff-item`, without +`note`, and should not automatically appear as ordinary Notes rows. Preserve +source/provenance terms (`manual`, `readwise`, `evernote`, `synced`) during edits. + +## Gutenberg sidebar + +The package-local sidebar searches Knowledge, filters by Knowledge Type, previews +notes and inserts/drags `pos/note` blocks. It runs in block editors, including +Stuff, but not classic Markdown editors. Do not port the old monolith's separate +Readwise/Evernote document panels. + +## Verification + +Test library filtering, raw taxonomy editing, content-based editor choice, +provenance preservation and sidebar insertion with Stuff active. On a fresh site, +exercise Add note through the real core REST flow and verify all required identity +and source terms exist. Missing terms have previously blocked creation despite +successful activation; this is an acceptance requirement, not a claim of a fix. diff --git a/packages/personal-stuff/TECHNICAL.md b/packages/personal-stuff/TECHNICAL.md index b69d929..5bc5e7c 100644 --- a/packages/personal-stuff/TECHNICAL.md +++ b/packages/personal-stuff/TECHNICAL.md @@ -90,3 +90,9 @@ The suite config mounts Stuff with Notes and the other packages. `.wp-env.person `PersonalStuffTest` covers idempotent/deferred raw terms without plugin options, Notes editor compatibility, private native REST rows, unknown content/unrelated term retention, permission-scoped filenames and raw-body upload derivatives. JS tests use the core serializer for gallery validity/order, unknown block retention, description isolation, and accent/typo/path search. The Jest command selects Node exports for dependencies while retaining JSDOM for block serialization. Browser checks should cover term parent changes, a camera/file upload, gallery caption/order, inline description editing, native Gutenberg round trips with Notes active, URL filters, collection navigation, responsive layout, and Print/PDF. Check anonymous app access and inspect attachment filenames and generated sizes. Do not cache authenticated responses in a broad service worker. + +## Design feedback + +[Session-derived UX feedback](../../docs/personal-stuff-ux-feedback.md) provides +context for search, photos and mobile editing. Use the current contract above +when older feedback describes superseded behavior. diff --git a/packages/personal-todo/TECHNICAL.md b/packages/personal-todo/TECHNICAL.md index eb243eb..8487813 100644 --- a/packages/personal-todo/TECHNICAL.md +++ b/packages/personal-todo/TECHNICAL.md @@ -5,6 +5,12 @@ It describes how a TODO is represented in WordPress, which values are durable or derived, and the lifecycle guarantees that the UI, REST API, Abilities API, ICS feed, and tests must share. +## App interaction + +Preserve compact quick-add, the DataViews list/filter/actions, and the full edit +modal with scheduling, recurrence, dependencies and history. Use shared Knowledge +Type terms in assignment controls instead of the old notebook controls. + ## Runtime Dependencies Personal TODO requires a WordPress Knowledge surface but does not require any From b0dbdb473cc4bbe99454046a9a30b5404bf4ced4 Mon Sep 17 00:00:00 2001 From: artpi Date: Tue, 8 Sep 2026 10:17:03 +0200 Subject: [PATCH 2/3] Retire Cursor-specific contributor rules --- .cursor/rules/cursor-rules.mdc | 63 ---------------- .cursor/rules/dev-environment.mdc | 9 --- .cursor/rules/layout.mdc | 9 --- .cursor/rules/update-agents-md.mdc | 9 --- .../wordpress-coding-standards-javascript.mdc | 46 ------------ .../wordpress-coding-standards-php.mdc | 71 ------------------- AGENTS.md | 9 +-- docs/todo.md | 3 - 8 files changed, 5 insertions(+), 214 deletions(-) delete mode 100644 .cursor/rules/cursor-rules.mdc delete mode 100644 .cursor/rules/dev-environment.mdc delete mode 100644 .cursor/rules/layout.mdc delete mode 100644 .cursor/rules/update-agents-md.mdc delete mode 100644 .cursor/rules/wordpress-coding-standards/wordpress-coding-standards-javascript.mdc delete mode 100644 .cursor/rules/wordpress-coding-standards/wordpress-coding-standards-php.mdc diff --git a/.cursor/rules/cursor-rules.mdc b/.cursor/rules/cursor-rules.mdc deleted file mode 100644 index 3eec536..0000000 --- a/.cursor/rules/cursor-rules.mdc +++ /dev/null @@ -1,63 +0,0 @@ ---- -description: Include this whenever discussing cursor rules. This has important information how to set up cursor rules for the project -globs: -alwaysApply: false ---- -# Creating new Cursor rules - -This rule explains exactly how to create new Cursor rules for this project. - -## Cursor rules location - -Always place rule files in PROJECT_ROOT/.cursor/rules/: - -IMPORTANT: never place rule files in any location outside of `.cursor/rules` - -## Cursor rule conventions - -- Follow the below naming convention: - - Use kebab-case for filenames - - Always use .mdc extension - - Make names descriptive of the rule's purpose -- You can link to other files in the project using Markdown link prefixed with mdc: (Example: `@app/Services/SeoService.php`) -- Only ever link directly to a file and not a directory - -## Cursor rules file structure - -Cursor rules are in the format of MDC (.mdc) which is like a regular Markdown file with frontmatter context. Below is an example: - -``` ---- -description: -globs: -alwaysApply: true/false (default to false unless otherwise instructed) ---- -# Rule Title - -Main content explaining the rule with markdown formatting. - -1. Step-by-step instructions -2. Code examples -3. Guidelines -``` - -Full rule example: - -``` ---- -description: This rule explains how to work with AI APIs like OpenAI and Anthropic -globs: -alwaysApply: false ---- -# How to handle AI integrations - -Whenever you use this rule, start your message with the following: - -"Analysing AI integrations..." - -- This project integrates with Anthropic and OpenAI via dedicated client classes located in @app/Clients. -- Service classes, potentially found in @app/Services, may abstract interactions with these clients. -- Ensure necessary API keys and configuration are present in the `.env` file (refer to `.env.example`). -- Follow existing patterns within the client or service classes for making API calls. -- Implement consistent error handling for API failures. -``` \ No newline at end of file diff --git a/.cursor/rules/dev-environment.mdc b/.cursor/rules/dev-environment.mdc deleted file mode 100644 index 8cf279e..0000000 --- a/.cursor/rules/dev-environment.mdc +++ /dev/null @@ -1,9 +0,0 @@ ---- -description: Local WordPress development, tests and environment repair -globs: .wp-env*.json,package.json,composer.json,tests/**,tools/** -alwaysApply: false ---- -Use [Development](../../docs/development.md) for current commands, fixture -boundaries, focused verification and routine local environment repair. -Use [Packaging](../../docs/packaging.md) for package versions and deployment. -Do not substitute legacy monolith commands for the current split-package flow. diff --git a/.cursor/rules/layout.mdc b/.cursor/rules/layout.mdc deleted file mode 100644 index 55b7bb2..0000000 --- a/.cursor/rules/layout.mdc +++ /dev/null @@ -1,9 +0,0 @@ ---- -description: PersonalOS repository layout and contributor guidance -globs: -alwaysApply: true ---- -Read [AGENTS.md](../../AGENTS.md) for the current architecture and task-specific -reference routing. Current plugins live in `packages/` with helpers in -`shared/php`. The root plugin, `modules/`, and `src-chatbot/` are legacy. -WordPress formatting does not apply to the separate Next.js app. diff --git a/.cursor/rules/update-agents-md.mdc b/.cursor/rules/update-agents-md.mdc deleted file mode 100644 index 768f9fc..0000000 --- a/.cursor/rules/update-agents-md.mdc +++ /dev/null @@ -1,9 +0,0 @@ ---- -description: Maintain current, scoped contributor and technical guidance -globs: AGENTS.md,packages/**/TECHNICAL.md,modules/**/TECHNICAL.md,docs/*.md -alwaysApply: false ---- -Follow [Maintaining instructions](../../AGENTS.md#maintaining-instructions). -Replace superseded rules instead of appending contradictory lessons. Put package -contracts beside their source, shared contracts in `docs/`, and link new current -contracts from AGENTS.md. Legacy module docs remain scoped to legacy work. diff --git a/.cursor/rules/wordpress-coding-standards/wordpress-coding-standards-javascript.mdc b/.cursor/rules/wordpress-coding-standards/wordpress-coding-standards-javascript.mdc deleted file mode 100644 index 3891398..0000000 --- a/.cursor/rules/wordpress-coding-standards/wordpress-coding-standards-javascript.mdc +++ /dev/null @@ -1,46 +0,0 @@ ---- -description: Official WordPress coding standards from https://github.com/WordPress/wpcs-docs/blob/master/wordpress-coding-standards/javascript.md -globs: src/*.js,src/*.jsx,src/*.ts,src/*.tsc -alwaysApply: false ---- -# WordPress JavaScript Coding Standards -Source: https://github.com/WordPress/wpcs-docs/blob/master/wordpress-coding-standards/javascript.md - -The WordPress JavaScript Coding Standards adapt the jQuery JavaScript Style Guide with these key modifications: -- Single quotes for strings instead of double quotes -- Case statements indented within switch blocks -- Consistent function content indentation -- WordPress-specific whitespace rules -- 100-character line limit encouraged but not enforced - -## Key Formatting Rules -- Indent with tabs, not spaces -- Liberal use of whitespace for readability -- Braces required for all blocks (if/else/for/while/try) -- Each statement on its own line -- Strict equality checks (`===`) over abstract equality (`==`) -- Single quotes for strings - -## Variable Declaration -- ES2015+: Use `const` by default, `let` when reassignment needed -- Pre-ES2015: Single `var` statement at top of function scope -- CamelCase for variables and functions -- UpperCamelCase for constructors and classes -- SCREAMING_SNAKE_CASE for constants - -## Code Organization -- Functions should be concise and focused -- Multi-line statements break after operators -- Comments precede relevant code with blank line before -- JSDoc format for documentation - -## Best Practices -- Array creation with `[]`, not `new Array()` -- Object creation with `{}` unless specific prototype needed -- Store loop maximum in variable rather than recalculating -- Use Underscore.js collection methods for data transformations -- Only use jQuery each() for jQuery collections - -## Tools -- JSHint for automated code quality checking -- Configuration in `.jshintrc` file diff --git a/.cursor/rules/wordpress-coding-standards/wordpress-coding-standards-php.mdc b/.cursor/rules/wordpress-coding-standards/wordpress-coding-standards-php.mdc deleted file mode 100644 index deebae2..0000000 --- a/.cursor/rules/wordpress-coding-standards/wordpress-coding-standards-php.mdc +++ /dev/null @@ -1,71 +0,0 @@ ---- -description: Official WordPress coding standards from https://github.com/WordPress/wpcs-docs/blob/master/wordpress-coding-standards/php.md -globs: *.php -alwaysApply: false ---- - -# WordPress PHP Coding Standards Summary -Source: https://github.com/WordPress/wpcs-docs/blob/master/wordpress-coding-standards/php.md - -## General Rules -- Use full PHP tags (``, never shorthand) -- Single quotes for strings without variables, double quotes when evaluating content -- No parentheses for `require`/`include` statements; use `require_once` for dependencies -- Files should end without closing PHP tag - -## Naming Conventions -- Variables, functions, action/filter hooks: lowercase with underscores (`some_function_name`) -- Classes, traits, interfaces, enums: capitalized words with underscores (`WP_Error`, `Walker_Category`) -- Constants: uppercase with underscores (`DOING_AJAX`) -- File names: lowercase with hyphens (`my-plugin-name.php`) -- Class files: prefix with `class-` (`class-wp-error.php`) - -## Whitespace & Indentation -- Use tabs for indentation, spaces for mid-line alignment -- Space after commas and around operators -- Space inside parentheses -- No space between function/method name and opening parenthesis -- No trailing whitespace at line ends -- Array items on new lines for multi-item arrays - -## Formatting -- Always use braces for control structures -- Arrays must use long syntax `array()` (not `[]`) -- One space between closing parenthesis and colon in return type declarations -- Always include trailing comma in multi-line arrays - -## Object-Oriented Programming -- One class/interface/trait/enum per file -- Always declare visibility (`public`, `protected`, `private`) -- Correct modifier order (e.g., `abstract` then `readonly` for class declarations) -- Always use parentheses for object instantiation - -## Control Structures -- Use `elseif` (not `else if`) -- Yoda conditions (`if ( true === $var )` not `if ( $var === true )`) -- Use braces even for single-statement blocks - -## Operators -- Ternary operators should test for true, not false -- Avoid error control operator (`@`) -- Prefer pre-increment/decrement (`++$i`) over post-increment/decrement (`$i++`) - -## Database -- Avoid direct database queries when possible -- Capitalize SQL keywords (`SELECT`, `UPDATE`, etc.) -- Use `$wpdb->prepare()` for secure queries -- Use placeholders (`%d`, `%f`, `%s`, `%i`) without quotes - -## Recommendations -- Use descriptive string values instead of boolean flags -- Prioritize readability over cleverness -- Use strict comparisons (`===` and `!==`) -- No assignments in conditionals -- Avoid `extract()`, `eval()`, `create_function()` and backtick operators -- Use PCRE over POSIX for regular expressions - -## Type Declarations -- Use lowercase for scalar types (`int`, `bool`, `string`, etc.) -- Ensure one space before and after type declarations -- No space between nullability operator (`?`) and type -- Obey PHP version compatibility constraints when using types diff --git a/AGENTS.md b/AGENTS.md index 63f06a0..31ab6b6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,10 +33,11 @@ an explicit behavior comparison. - Prefer native WordPress APIs, storage and components. Add helpers only for a meaningful domain rule, useful reuse, or an intentional override point. Avoid speculative registries, migration layers and broad style refactors. -- Apply the repo's PHP/JS standards when changing that language; see - `.cursor/rules/wordpress-coding-standards/`. The optional personal - `artpi-wp-php-style` skill supplements the repo's actual lint configuration. - Do not impose WordPress formatting on the separate legacy Next.js app. +- Apply the repository's PHPCS and WordPress Scripts lint configuration when + changing PHP or JavaScript; use the focused commands in `docs/development.md`. + The optional personal `artpi-wp-php-style` skill supplements the actual lint + configuration. Do not impose WordPress formatting on the separate legacy + Next.js app. - Match validation to the changed behavior. UI work needs a browser check of the interaction and resulting state; activation or a build alone is not UI verification. See the development guide for commands and test boundaries. diff --git a/docs/todo.md b/docs/todo.md index 71ff480..7113365 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -17,7 +17,6 @@ These are roughly the features I want to implement. They are meant to be - We need proper Unit Testing for everything - Notes: Note should be attachable to URLs. Then we can write a Chrome plugin or something similar to display "notes" once URL is visited. - We need a tool to send emails -- Docs: Write cursor rules and docs explaining how to create a new module and a tool. - Openverse has some good epic soundtracks for the background of motivational prompt: https://openverse.org/search/audio?q=epic+cinematic+motivational - Need to implement UX for Notebooks, similar to how Todos are using Data Views. Then implement a dashboard for Bucketlists. - Need integration with Google Calendar, Google Docs, Google Drive and other google services to sync data. @@ -53,5 +52,3 @@ These are roughly the features I want to implement. They are meant to be --- - Explanation how saving convesation works -- Cursor rule about running tests after every big change -- Cursor rule about noting what you learned From 5923c8013b351e5f5152c14f014d103ec0e59854 Mon Sep 17 00:00:00 2001 From: artpi Date: Tue, 8 Sep 2026 21:16:10 +0200 Subject: [PATCH 3/3] Describe empty Knowledge Type REST terms --- docs/knowledge-contract.md | 6 + packages/personal-stuff/TECHNICAL.md | 1 + .../includes/class-personal-stuff-plugin.php | 2 + packages/personal-stuff/personal-stuff.php | 4 +- packages/personal-stuff/readme.txt | 5 +- ...s-personalos-knowledge-type-vocabulary.php | 175 ++++++++++++++++++ tests/unit/PersonalStuffTest.php | 26 +++ 7 files changed, 216 insertions(+), 3 deletions(-) diff --git a/docs/knowledge-contract.md b/docs/knowledge-contract.md index 16b9aba..8c17b2c 100644 --- a/docs/knowledge-contract.md +++ b/docs/knowledge-contract.md @@ -35,6 +35,12 @@ raw terms, including system/container terms. Stuff's app assigns leaf terms but also preserves unrelated terms. Do not impose a global prohibition that changes the native editor. See each package contract for its identity and assignment rules. +The core Knowledge Type REST collection preserves stored term descriptions. When +Personal Stuff is active and a description is empty, its shared vocabulary adds +a response-only derived role and root-to-leaf name/ID path, so constrained +clients can classify a paginated term without resolving its parents. This never +writes generated text to WordPress term storage. + ## Integration boundaries Shared base classes own settings, assets, logging and Knowledge access without a diff --git a/packages/personal-stuff/TECHNICAL.md b/packages/personal-stuff/TECHNICAL.md index 5bc5e7c..fb6a762 100644 --- a/packages/personal-stuff/TECHNICAL.md +++ b/packages/personal-stuff/TECHNICAL.md @@ -64,6 +64,7 @@ PHP passes paths discovered with `rest_get_route_for_post_type_items` and `rest_ - Knowledge collection/item CRUD: `/wp/v2/knowledge`, using `context=edit` for raw content. - Terms: resolved core Knowledge Type REST collection; paginate all terms and expand descendants in JS. +- Empty term descriptions in that core collection receive a response-only, derived role and root-to-leaf path with term IDs. Stored descriptions are never changed. - Files: `/wp/v2/media`, with `post=` on upload. Every permission decision is WordPress's existing post, term or attachment permission. Taxonomy terms retain the Knowledge runtime's native visibility. No claim is made that raw term names/descriptions are private merely because the app is private. diff --git a/packages/personal-stuff/includes/class-personal-stuff-plugin.php b/packages/personal-stuff/includes/class-personal-stuff-plugin.php index 3494e9a..0d93cd5 100644 --- a/packages/personal-stuff/includes/class-personal-stuff-plugin.php +++ b/packages/personal-stuff/includes/class-personal-stuff-plugin.php @@ -36,6 +36,8 @@ public function __construct() { /** Register only native integrations; no package REST or storage. */ public function register() { + $this->vocabulary()->register_type_labels(); + $this->vocabulary()->register_rest_descriptions(); $this->register_missing_knowledge_notice(); $this->register_wp_app( array( $this, 'render_app' ) ); add_action( 'admin_menu', array( $this, 'add_admin_menu' ) ); diff --git a/packages/personal-stuff/personal-stuff.php b/packages/personal-stuff/personal-stuff.php index fed6ea0..0f31f44 100644 --- a/packages/personal-stuff/personal-stuff.php +++ b/packages/personal-stuff/personal-stuff.php @@ -2,7 +2,7 @@ /** * Plugin Name: Personal Stuff * Description: Find and organize belongings, places, tags, and photos stored as Knowledge records. - * Version: 0.1.0 + * Version: 0.1.1 * Requires at least: 7.0 * Requires PHP: 7.4 * Author: Artur Piszek @@ -17,7 +17,7 @@ exit; } -define( 'PERSONAL_STUFF_VERSION', '0.1.0' ); +define( 'PERSONAL_STUFF_VERSION', '0.1.1' ); define( 'PERSONAL_STUFF_FILE', __FILE__ ); /** diff --git a/packages/personal-stuff/readme.txt b/packages/personal-stuff/readme.txt index 278e916..7654de6 100644 --- a/packages/personal-stuff/readme.txt +++ b/packages/personal-stuff/readme.txt @@ -4,7 +4,7 @@ Tags: inventory, knowledge, photos Requires at least: 7.0 Tested up to: 7.1 Requires PHP: 7.4 -Stable tag: 0.1.0 +Stable tag: 0.1.1 License: GPL-2.0-or-later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -53,5 +53,8 @@ Source is included under `src/`. In the PersonalOS repository run `npm ci`, `com == Changelog == += 0.1.1 = +Empty Knowledge Type REST descriptions now include a derived role and complete term path without changing stored term descriptions. + = 0.1.0 = Initial Knowledge-backed Personal Stuff app. diff --git a/shared/php/class-personalos-knowledge-type-vocabulary.php b/shared/php/class-personalos-knowledge-type-vocabulary.php index 84de0ff..193fa2b 100644 --- a/shared/php/class-personalos-knowledge-type-vocabulary.php +++ b/shared/php/class-personalos-knowledge-type-vocabulary.php @@ -10,6 +10,13 @@ * Owns Personal shared Knowledge type terms. */ class PersonalOS_Knowledge_Type_Vocabulary { + /** + * Whether the core Knowledge Type REST response filter is registered. + * + * @var bool + */ + private static $rest_descriptions_registered = false; + /** * Knowledge bridge. * @@ -35,6 +42,174 @@ public function register_type_labels() { add_filter( 'wp_knowledge_types', array( $this, 'filter_type_labels' ) ); } + /** + * Register derived descriptions on core Knowledge Type REST responses. + * + * @return void + */ + public function register_rest_descriptions() { + if ( ! self::$rest_descriptions_registered ) { + add_filter( 'rest_prepare_wp_knowledge_type', array( $this, 'add_rest_term_description' ), 10, 3 ); + self::$rest_descriptions_registered = true; + } + } + + /** + * Describe otherwise empty Knowledge Type terms in core REST responses. + * + * Native term descriptions remain user-authored content. The response-only + * fallback lets clients that cannot resolve the full term hierarchy identify + * structural terms and Personal Stuff places from a single paginated row. + * + * @param WP_REST_Response $response REST response. + * @param WP_Term $term Prepared Knowledge Type term. + * @param WP_REST_Request $request REST request. + * @return WP_REST_Response + */ + public function add_rest_term_description( WP_REST_Response $response, WP_Term $term, WP_REST_Request $request ) { + if ( '' !== trim( $term->description ) ) { + return $response; + } + + $data = $response->get_data(); + $data['description'] = $this->rest_term_description( $term ); + $response->set_data( $data ); + + return $response; + } + + /** + * Build a derived description for a Knowledge Type REST row. + * + * @param WP_Term $term Knowledge Type term. + * @return string + */ + private function rest_term_description( WP_Term $term ) { + $description = $this->rest_term_role_description( $term ); + $path = $this->rest_term_path( $term ); + + return sprintf( + /* translators: 1: Knowledge Type role, 2: hierarchical term path. */ + __( '%1$s Path: %2$s', 'personalos' ), + $description, + implode( ' › ', $path ) + ); + } + + /** + * Get the derived role description for a Knowledge Type term. + * + * @param WP_Term $term Knowledge Type term. + * @return string + */ + private function rest_term_role_description( WP_Term $term ) { + $path_slugs = $this->rest_term_path_slugs( $term ); + + if ( in_array( 'stuff-places', $path_slugs, true ) ) { + return __( 'Personal Stuff place.', 'personalos' ); + } + + if ( in_array( 'stuff-tags', $path_slugs, true ) ) { + return __( 'Personal Stuff tag.', 'personalos' ); + } + + $descriptions = array( + 'artifact' => __( 'Knowledge artifact container.', 'personalos' ), + 'note' => __( 'Personal Notes type.', 'personalos' ), + 'daily-note' => __( 'Personal Notes daily-note type.', 'personalos' ), + 'todo' => __( 'Personal TODO type.', 'personalos' ), + 'conversation' => __( 'Personal AI Chat conversation type.', 'personalos' ), + 'memory' => __( 'Knowledge memory type.', 'personalos' ), + 'skill' => __( 'Knowledge skill type.', 'personalos' ), + 'source' => __( 'Knowledge source container.', 'personalos' ), + 'manual' => __( 'Manually created Knowledge source.', 'personalos' ), + 'synced' => __( 'Synced Knowledge source.', 'personalos' ), + 'readwise' => __( 'Readwise Knowledge source.', 'personalos' ), + 'evernote' => __( 'Evernote Knowledge source.', 'personalos' ), + 'ai-chat' => __( 'Personal AI Chat Knowledge source.', 'personalos' ), + 'personalos' => __( 'PersonalOS Knowledge source.', 'personalos' ), + 'status' => __( 'Knowledge status container.', 'personalos' ), + 'inbox' => __( 'Knowledge inbox status.', 'personalos' ), + 'now' => __( 'Knowledge current-status type.', 'personalos' ), + 'later' => __( 'Knowledge later-status type.', 'personalos' ), + 'follow-up' => __( 'Knowledge follow-up status.', 'personalos' ), + 'project' => __( 'PARA projects container.', 'personalos' ), + 'area' => __( 'PARA areas container.', 'personalos' ), + 'resource' => __( 'PARA resources container.', 'personalos' ), + 'reference' => __( 'PARA reference type.', 'personalos' ), + 'archive' => __( 'PARA archive container.', 'personalos' ), + 'starred' => __( 'Knowledge starred marker.', 'personalos' ), + 'stuff' => __( 'Personal Stuff container.', 'personalos' ), + 'stuff-item' => __( 'Personal Stuff item type.', 'personalos' ), + 'stuff-places' => __( 'Personal Stuff places container.', 'personalos' ), + 'stuff-tags' => __( 'Personal Stuff tags container.', 'personalos' ), + ); + + return isset( $descriptions[ $term->slug ] ) ? $descriptions[ $term->slug ] : __( 'Knowledge Type.', 'personalos' ); + } + + /** + * Get the term path formatted with names and IDs. + * + * @param WP_Term $term Knowledge Type term. + * @return string[] + */ + private function rest_term_path( WP_Term $term ) { + $terms = $this->rest_term_path_terms( $term ); + + return array_map( + function ( $path_term ) { + return sprintf( + /* translators: 1: Knowledge Type name, 2: Knowledge Type ID. */ + __( '%1$s (%2$d)', 'personalos' ), + $path_term->name, + $path_term->term_id + ); + }, + $terms + ); + } + + /** + * Get the term slugs in its root-to-leaf path. + * + * @param WP_Term $term Knowledge Type term. + * @return string[] + */ + private function rest_term_path_slugs( WP_Term $term ) { + return array_map( + function ( $path_term ) { + return $path_term->slug; + }, + $this->rest_term_path_terms( $term ) + ); + } + + /** + * Get a term's root-to-leaf path without following malformed cycles. + * + * @param WP_Term $term Knowledge Type term. + * @return WP_Term[] + */ + private function rest_term_path_terms( WP_Term $term ) { + $terms = array( $term ); + $seen_ids = array( (int) $term->term_id ); + $parent_id = (int) $term->parent; + + while ( $parent_id && ! in_array( $parent_id, $seen_ids, true ) ) { + $parent = get_term( $parent_id, $this->bridge->type_taxonomy() ); + if ( ! $parent || is_wp_error( $parent ) ) { + break; + } + + array_unshift( $terms, $parent ); + $seen_ids[] = (int) $parent->term_id; + $parent_id = (int) $parent->parent; + } + + return $terms; + } + /** * Adds labels for PersonalOS terms. * diff --git a/tests/unit/PersonalStuffTest.php b/tests/unit/PersonalStuffTest.php index 55f98d6..a5c3ffa 100644 --- a/tests/unit/PersonalStuffTest.php +++ b/tests/unit/PersonalStuffTest.php @@ -47,6 +47,32 @@ public function test_terms_are_idempotent_raw_terms_without_plugin_options() { $this->assertSame( $before, $wpdb->get_col( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE 'personal_stuff_%'" ) ); } + public function test_core_term_rest_descriptions_explain_empty_terms_without_changing_stored_descriptions() { + $this->plugin->vocabulary()->register_rest_descriptions(); + $place = wp_insert_term( 'Blaszak', 'wp_knowledge_type', array( 'parent' => $this->ids['stuff-places'] ) ); + $tag = wp_insert_term( 'Travel', 'wp_knowledge_type', array( 'parent' => $this->ids['stuff-tags'], 'description' => 'Used away from home.' ) ); + $server = rest_get_server(); + do_action( 'rest_api_init', $server ); + $request = new WP_REST_Request( 'GET', rest_get_route_for_taxonomy_items( 'wp_knowledge_type' ) ); + $request->set_param( 'context', 'edit' ); + $request->set_param( 'hide_empty', false ); + $response = $server->dispatch( $request ); + $terms = array_column( $response->get_data(), null, 'id' ); + + $this->assertSame( 200, $response->get_status() ); + $this->assertSame( + sprintf( + 'Personal Stuff place. Path: Stuff (%d) › Places (%d) › Blaszak (%d)', + $this->ids['stuff'], + $this->ids['stuff-places'], + $place['term_id'] + ), + $terms[ $place['term_id'] ]['description'] + ); + $this->assertSame( 'Used away from home.', $terms[ $tag['term_id'] ]['description'] ); + $this->assertSame( '', get_term( $place['term_id'], 'wp_knowledge_type' )->description ); + } + public function test_provisioning_can_be_deferred_until_knowledge_exists() { unregister_taxonomy( 'wp_knowledge_type' ); $this->assertSame( array(), $this->plugin->ensure_terms() );