Make the document UI reusable as published building blocks#957
Open
backnotprop wants to merge 60 commits into
Open
Make the document UI reusable as published building blocks#957backnotprop wants to merge 60 commits into
backnotprop wants to merge 60 commits into
Conversation
bd2691c to
e0a46ac
Compare
d8c109a to
cdd6503
Compare
…ted reuse plan The document-ui extraction/cutover (ADRs 002/003) was an AI-driven rewrite that broke the app; the code was reverted. Add ADR 004 as the source of truth: share @plannotator/ui as published building blocks for the Workspaces app, keep Plannotator's app unchanged, gate on human-verified parity. Banner the reverted ADRs and point AGENTS.md/CLAUDE.md at 004 so future agents don't rebuild the mess.
…inventory 36-agent verification of the reuse inventory: confirmed the /api coupling but found the draft missed Viewer's transitive backend call, the cookie settings layer, 3 React contexts + identity singleton, SSE transports, and harder packaging blockers. Adds the verified per-subsystem extraction plan with a parity guardrail on every step; flags the draft inventory as superseded.
Phase 0-7 execution roadmap (safety net -> packaging -> foundation seams -> rendering -> navigation -> comments -> extras -> publish) and the reusable 'did it break?' parity checklist run after every step. Both enforce the law: move + decouple, never rewrite; Plannotator's experience cannot change.
…ime change Phase 0: captured parity baseline (typecheck/test/build + shipped-bundle hashes). Phase 1 packaging fixes to packages/ui, metadata only: - add phantom dompurify ^3.3.3 dep (imported in sanitizeHtml/aiChatFormat, was undeclared) - align diff ^8.0.3 -> ^8.0.4 with root - add peerDependencies (react, react-dom, tailwindcss, tailwindcss-animate); keep as devDeps - add files allowlist (excludes tests); remove dead tsconfig @plannotator/shared alias Verified byte-identical: typecheck pass, 1620 tests pass/0 fail, all 3 builds OK, shipped plan+review bundle hashes unchanged from baseline. Remaining Phase 1 blocker (@plannotator/ai + @plannotator/shared workspace:* deps) deferred pending a publish-vs-inline decision; logged in worklog.
getImageSrc now delegates to a module-level resolver defaulting to the verbatim Plannotator /api/image logic; add setImageSrcResolver/resetImageSrcResolver so a host (Workspaces) can resolve images via its own backend. All 5 consumers and the signature unchanged. Verified: default URLs byte-identical, typecheck pass, 1620 tests pass/0 fail, builds OK. No Plannotator behavior change.
…am 2) storage.ts cookie impl is now the default 'cookieBackend'; add setStorageBackend/ resetStorageBackend so a host (Workspaces) can persist settings via its own storage. getItem/setItem/removeItem delegate to the active backend; the ~24 consumers and literal plannotator-* keys are unchanged. Verified: swap works, typecheck pass, 1620 tests pass/0 fail, builds OK, theme persists across reload.
Add optional mode? prop; mode now mode ?? resolvedMode. Plannotator passes no mode (App.tsx:4261) so it keeps using ThemeProvider's resolvedMode unchanged. A host without ThemeProvider can supply mode directly. Verified: typecheck pass, 1620 tests/0 fail, builds OK, App.tsx untouched.
Viewer gains optional disableCodePathValidation? threaded to a new disabled? arg on useValidatedCodePaths; when set, the /api/doc/exists probe is skipped. Default undefined for Plannotator => validation stays on, /api/doc/exists fires exactly as today. Verified: typecheck pass, 1620 tests/0 fail, builds OK, App.tsx untouched. Also logs Phase 3 workflow outcome + remaining scroll/docfetch pieces.
Add DocPreviewFetcher seam (default = verbatim /api/doc fetch) + setDocPreviewFetcher/resetDocPreviewFetcher; route handleMouseEnter through it, useCallback deps unchanged. No caller overrides it => Plannotator fetches /api/doc identically. typecheck pass, 1620 tests/0 fail, builds OK.
Add render-transparent ScrollViewportProvider (createElement, keeps .ts) so the scroll-viewport context travels with @plannotator/ui instead of living only in App.tsx. Rewire App.tsx provider tags (3-line delta); identical tree/value/ position, sidebar TOC still reads the MAIN viewport. Fix stale OverlayScrollbars doc-comment. typecheck pass, 1620 tests/0 fail, builds OK, eyeball: TOC tracks.
…elf-review) The Phase-3 disabled branch set ready=true with an empty map, which makes gateCodePath demote every code link to plain text. Leave ready=false so the no-validation fallback renders links optimistically. No Plannotator impact (never disables). Logs Phase 3 completion + reusability note. typecheck pass, 1620 tests/0 fail, builds OK.
Lift useFileBrowser's three backend wires (load-dir fetch, obsidian-vault fetch, and the SSE live-watch effect moved VERBATIM) into an injectable FileTreeBackend with default + setFileTreeBackend/resetFileTreeBackend, same pattern as the image /storage seams. useFileBrowser() stays zero-arg; default fetch/SSE URLs identical. Sidebar confirmed noop (zero backend wires, already reused by review-editor). Verified: useFileBrowser.test.tsx passes 6/0 UNMODIFIED (DOM_TESTS=1), typecheck pass, 1620 tests/0 fail, builds OK, App.tsx untouched, manual eyeball (annotate adr/: tree loads, file-switch works, new file appears live via SSE). Plannotator byte-unchanged. Logs two pre-existing bugs found during testing (not regressions).
…ons/drafts) Five-probe code research of the comment system. Key finding: most comment UI is already portable (panel/popover/toolbar/highlighter prop-driven; review-editor already reuses the hooks). Phase 5 narrows to 3 seams — draft transport (+ the 3-party generation protocol), external-annotation transport (SSE->polling, move verbatim), and identity/authorship — plus 2 non-extraction items: renderer coupling (document as a contract) and replies/threading (defer as a new feature).
…rridable (Phase 5) Three seams (identity, draft transport, external-annotation transport), each defaulting to today's behavior; renderer coupling documented as a contract; replies/threading deferred as a new feature. Locks in the recommended choices from the Phase 5 spec/synthesis.
Add IdentityProvider + setIdentityProvider/resetIdentityProvider in identity.ts; getIdentity/isCurrentUser now delegate to a module-level provider defaulting to today's ConfigStore tater behavior. The ~9 author-stamp sites and 2 (me)-badge sites delegate with zero call-site edits. No caller overrides => Plannotator byte-unchanged. typecheck pass, 1620 tests/0 fail, builds OK.
…seam 2) Add DraftTransport (load/save/remove) + getDraftTransport/setDraftTransport/ resetDraftTransport in useAnnotationDraft.ts, default = today's /api/draft fetches verbatim. useCodeAnnotationDraft reads getDraftTransport() live. The generation pre-increment, 500ms debounce, keepalive retry-gate, and pagehide/visibilitychange flush stay in the hooks; getDraftGeneration() still escapes to the host. save rejects-on-failure so the gated retry is preserved. No caller overrides => Plannotator byte-unchanged. shared/draft.test.ts 10/0, annotationDraftPersistence 13/0, typecheck pass, 1620 tests/0 fail, builds OK.
…5 seam 3) Add ExternalAnnotationTransport<T> (subscribe/getSnapshot/CRUD) + setters in useExternalAnnotations.ts; default = today's SSE->polling wire moved verbatim into createDefaultTransport. The reducer (applyEvent), fallback-once gate, 500ms poll, versionRef scoping, optimistic-before-await, and [enabled] gate stay in the hook. A host (Workspaces) can implement the same event contract over Durable Objects. No override caller => Plannotator byte-unchanged. external-annotations test green, typecheck pass, 1620 tests/0 fail, builds OK. Logs Phase 5 completion.
…s, sharing, AI) Five-probe code research. Most of the four subsystems is already portable; the real work is 5 seams (version fetchers + vscode-diff, config write-back, obsidian detect, save-to-notes, AI transport) + 1 CSS move (block/raw diff classes from the app shell into the package's theme.css). Fragile do-not-touch: the AI SSE reader loop + epoch guards, and configStore debounce/deepMerge. Five Plannotator-only pieces (OpenInApp, HooksTab, useUpdateCheck, useAgents/useAgentJobs) stay home.
…) host-overridable (Phase 6) Five seams + one CSS move, each defaulting to today's behavior. AI reader loop + epoch guards and configStore debounce/deepMerge stay verbatim. Five Plannotator- only pieces stay home. Locks the recommended choices from the Phase 6 spec.
…diff CSS into package (Phase 6 versions) usePlanDiff gains optional fetchers (default /api/plan/version(s), error asymmetry kept: selectBaseVersion alerts, fetchVersions silent). PlanDiffViewer gains optional onOpenVscodeDiff (default /api/plan/vscode-diff). Relocate .annotation-highlight* + .plan-diff-* block/raw CSS from editor/index.css into ui/theme.css (next to .plan-diff-word-*) so the diff/highlights are self-styling from the package. Verified: relocated CSS gone from index.css, present in shipped bundle (33x), diff renders identical; typecheck pass, 1620 tests/0 fail, builds OK, App.tsx untouched.
…Phase 6 settings) configStore.setServerSync(fn) injects only the terminal POST /api/config; the 300ms debounce, deepMerge batching, singleton, and eager cookie reads stay verbatim. Settings gains optional onDetectObsidianVaults (default /api/obsidian/vaults), with the [obsidian.enabled] effect dep + auto-select-first-vault verbatim. No override caller => Plannotator unchanged. typecheck pass, 1620 tests/0 fail, builds OK.
ExportModal gains optional onSaveToNotes (default = verbatim POST /api/save-notes); showNotesTab = isApiMode && !!markdown kept byte-for-byte. Sharing utils already parameterized (noop). No override caller => Plannotator unchanged. typecheck pass, 1620 tests/0 fail, builds OK.
useAIChat gains a module-level AITransport (session/query/abort/permission) + setAITransport/resetAITransport, default = the five /api/ai/* fetches verbatim. The SSE reader loop, epoch/createRequest guards, and the supersede-abort position inside createSession stay untouched. Capabilities + provider-resolution stay host-owned in App.tsx. No override caller => Plannotator unchanged. ai.test.ts 97/0, typecheck pass, 1620 tests/0 fail, builds OK.
…atorUI front door (Phase 7 step 4)
…k (Phase 7 step 5)
…tep 6) Add one override test per seam (setX(fake)→drive→assert→resetX()) for all 9 seams + loadFromBackend, modeled after the existing seam test pattern. Fix configure.test.ts to defer mock.module() into beforeAll and restore with captured real function references in afterAll so sibling seam test files are not poisoned by spy replacements in the shared Bun worker module registry.
…istency - Bump @plannotator/ui to 0.21.0 (lockstep with @plannotator/core + repo, per ADR 007) [was the 1 critical review finding] - useAnnotationDraft: route persistNow/dismissDraft save+remove through getDraftTransport() so all paths read the transport consistently (matches the load path; makes the single-global invariant explicit) - configStore.loadFromBackend: document it must be called BEFORE init() or server values get overwritten - packages/core/tsconfig: add explicit types:[] so the node-free invariant is first-class (verified: planted node:fs still fails TS2882)
Rebased onto origin/main (picks up #948 draft-deletion fix, the 0.21.1 bump, and the #949/#950 editor fix). The rebase auto-merged #948's code-draft logic (hasHadAnnotationsRef, empty-state tombstone, clearTimeout in restore/dismiss) with the Phase-5 transport refactor cleanly — except the empty-state tombstone delete was left as a raw fetch('/api/draft', DELETE). Route it through getDraftTransport().remove() so a host backend tombstones its own stored draft on clear (the #948 guarantee, for hosts). Plannotator unchanged (default transport hits the same endpoint). Bump @plannotator/core + @plannotator/ui 0.21.0 -> 0.21.1 to match main's version (lockstep per ADR 007). Verified: typecheck clean, madge no-cycles, plain suite 1637 pass / 0 fail, #948 draft-clear test 3/0. (The 45 DOM_TESTS failures are the known server/network integration tests that need a real OS env — same set on main, not regressions.)
- PlanDiffViewer: wrap onOpenVscodeDiff in try/finally so a host opener that throws can't wedge the VS Code button in a permanent loading state (default unaffected) - useExternalAnnotations: (re-)capture the transport inside the effect on enable so a host that installs a transport before enabling annotations is honored, not the stale default — keeps the split-transport fix (effect + CRUD share one ref) - configure.ts: import ServerSyncFn from configStore instead of duplicating the type - repoint the 2 remaining @plannotator/shared test imports to @plannotator/core - AGENTS.md/CLAUDE.md: document the new packages/core package All host-path only — Plannotator behavior unchanged. typecheck clean, no cycles, full suite green. Skipped (not simple/over-engineering): usePlanDiff prop->module-level (design change), Obsidian late-bind, getSnapshot guard (inert), transport <any> (variance).
The branch had accumulated ~6,200 lines of ADR scaffolding (6 decisions, 7 specs, 10 research spikes/synthesis, 6 worklogs/roadmaps/plans) for this one effort. Replace all of it with a single concise README that ships with the published package: what @plannotator/ui + @plannotator/core are, why they exist (commercial reuse), how the host-override seams work (configurePlannotatorUI), how a consumer installs/builds, and the one rule (don't reimplement from scratch — add a seam). Repoint the CLAUDE.md banner at the README. No code references the deleted docs; main's pre-existing adr/ docs untouched.
Directory-scoped agent guidance for anyone editing @plannotator/ui: don't rewrite from scratch, add a seam (default = today's behavior, Plannotator byte-for-byte unchanged), core stays node-free, never delete working code until human parity. Points to README.md for the architecture. CLAUDE.md -> AGENTS.md symlink mirrors the repo root convention.
madge is unmaintained (~3 years stale) and the check was never wired into CI, so it was a dormant script + devDependency on a load-bearing path. Drop it: remove the check:cycles script, the madge devDependency, and .madgerc. The no-cycle invariant still holds by construction — @plannotator/core imports nothing (zero @plannotator deps in its package.json), so any accidental core->shared/ui import fails at publish-time bun pm pack (and review). No automated tripwire, but no stale unmaintained tooling either.
- useExternalAnnotations: declare unsubscribe as let (not const) + guard calls, so a host transport that fires onError synchronously during subscribe falls back to polling instead of throwing a TDZ ReferenceError (Plannotator's EventSource fires async, never hit) - package.json: add explicit ./components/html-viewer export (dir has index.ts; the ./components/* -> *.tsx wildcard can't resolve it, so external installers would fail) - README: fix configurePlannotatorUI sample keys to the real option names (storageBackend/identityProvider/imageSrcResolver/externalAnnotationTransport) - AGENTS.md: point the Ask-AI mapping at packages/core/agents.ts (shared/agents.ts is a shim now) All publish/host-path/doc only — Plannotator unchanged. (#1 CSS-build font collision deferred to publish-prep — it needs the asset pipeline + files allowlist, not a one-liner.)
…ts (review #1) Industry standard for a shared UI package: ship theme + component CSS, let the consuming app load fonts. Drop the @fontsource imports from styles-entry.css (the publish CSS entry); the theme still defines --font-sans/--font-mono, and the app provides those families. Fixes the asset-name collision (every emitted .woff2 was renamed styles.css) and shrinks the published stylesheet 555kB -> 185kB. README documents the two-line @fontsource install. Plannotator unaffected: its apps (editor/review-editor index.css) load fonts via their own entry CSS — styles-entry.css is consumed ONLY by the publish CSS build.
prepublishOnly doesn't run for npm pack / bun pm pack / git / file: installs, so the package exported ./styles.css without shipping it. prepack runs on any pack, so the stylesheet is always present. Verified: bun pm pack now emits styles.css.
…table AI abort seam Rebased onto main (0.21.3). Bump @plannotator/core + @plannotator/ui to 0.21.3 to stay in lockstep with the repo version. Resolve the useAIChat conflict: main added postServerAbort (an awaitable abort that prevents session-busy races) using a raw fetch. Route it through the AITransport seam by making AITransport.abort return Promise<unknown> instead of void, so the host override is honored AND main's await-the-abort behavior is preserved. Update the abort mocks in the seam/configure tests accordingly.
The await site in ask() relies on postServerAbort resolving so a superseding query can proceed. main's original guaranteed this with its own .catch on the fetch; routing through the AITransport seam delegated that guarantee to the transport. Restore it at the call site (Promise.resolve(...).catch) so a host override that rejects — or returns void at runtime — can't throw out of ask().
- useAIProviderConfig: import Origin from @plannotator/core/agents (was the only ui file still importing @plannotator/shared); drop the masking shared/* path alias from ui/tsconfig.json so a stray shared import now fails typecheck. The hook is part of the published surface — a standalone install had no @plannotator/shared to resolve. - useAIChat.postServerAbort: defer the transport call into .then so a host abort that throws *synchronously* also can't reject (the .catch only caught async). - useExternalAnnotations: default getSnapshot returns null (skip) on a malformed 200 instead of coercing to []/0, so it can't clear annotations or reset the version cursor — restoring the pre-seam behavior.
Two override points the Workspaces app needs that had no seam: - UploadTransport (utils/upload.ts): image attachments hardcoded POST /api/upload with no override. Add a setX/resetX/getX seam (default = today's /api/upload, verbatim) and route AttachmentsButton through it. Workspaces sends bytes to its R2 asset API and returns the content-addressed URL. - IdentityProvider.isEditable() (utils/identity.ts): the Settings rename/regenerate controls wrote to the cookie store, bypassing a host identity provider — so a host with server-owned identity could split one user across two author names. Add an optional isEditable() (default true) and hide the rename controls when a host returns false. Plannotator's cookie identity stays editable — unchanged. Both wired into configurePlannotatorUI(); seam tests added; configure routing test covers uploadTransport. HANDOFF.md updated with the Workspaces seam mapping from the repo research (asset layer, identity, realtime, no-AI-infra, the Me display-name backend follow-up). README publish command corrected to bun pm pack + npm publish.
Self-review: the deferred .then read sessionIdRef.current a microtask after the guard checked it. Capture the id synchronously so the abort always targets the session current at call time and there's no double-read.
…arden abort - configStore.loadFromBackend: seed the host StorageBackend with resolved defaults for keys it lacks. The constructor runs at module load (before a host installs its backend), so its default-seeding writes went to the cookie backend; without this a fresh host store was never populated and generated defaults (e.g. displayName) regenerated every reload. [P1, host path] - Viewer.tsx: replace NodeJS.Timeout with ReturnType<typeof setTimeout> (2 refs) so a browser-only consumer compiling the published source doesn't need @types/node. Matches the pattern already used in configStore. [P1, published path] - useAIChat: harden the create-session supersede abort the same way as postServerAbort, so a host transport that throws can't surface an unhandled rejection. No impact on Plannotator (default self-catches). [nit] - .gitignore: correct stale 'prepublishOnly' comment to 'prepack'. [nit] Plannotator behavior unchanged (it never calls loadFromBackend; the timer/abort changes are behavior-preserving). Strengthened configStore seam test to assert first-run seeding. typecheck clean, 1773 pass / 0 fail.
Self-review: the hardened abort pattern (defer into .then + .catch so a host transport that throws can't reject) was duplicated across postServerAbort and the create-session supersede site — the exact drift the review flagged. Extract a module-level safeAbort(sessionId) so both call sites share one hardened implementation and can't diverge again. Behavior unchanged; reads aiTransport at call time so a late override is honored.
Rebased onto main (0.21.4, adds markdown math #878 + parser hardening). Bump @plannotator/core + @plannotator/ui to 0.21.4 to stay in lockstep with the repo. katex (main's math dep) merged into ui; typecheck clean, 1810 pass / 0 fail.
cdd6503 to
6e3eb2f
Compare
- HANDOFF.md: add supported-imports allowlist vs unsupported (hardcoded /api/*) list; document the annotation anchor schema, reattachment order, and untested stale-anchor degradation; state that the markdown editor cannot take CM6/Yjs extensions yet and the plan of record; note AI avoidability re-verified post-rebase; fix stale 0.21.3 ref. - adr/decisions/005: record the publish-as-packages decision (packages over copy/vendor, core/ui split, seam-singleton pattern + SSR revisit condition, the law, lockstep publish model).
…e barrel Consumers compile the published TS source with their own compiler options, and strict mode failed with 35 errors inside the package: - settings.ts: satisfies SettingDef<unknown> is contravariantly illegal under strictFunctionTypes (33 errors) — use SettingDef<any> - useDismissOnOutsideAndEscape: RefObject<HTMLElement> rejects React 19's useRef<T>(null) refs — widen to HTMLElement | null - globals.d.ts: declare *.png / *.webp modules, referenced from each asset-importing component so any consumer program that includes one gets the ambient declarations Also unscatter the seam contract types: configure.ts re-exports every seam type next to configurePlannotatorUI, and ServerSyncFn is now exported from config/index.ts (it was unreachable through the exports map). Verified: standalone Vite consumer importing the full supported surface passes tsc --noEmit under full strict (was 35 errors).
…, was 1.6MB) Main's math PR imports katex/dist/katex.min.css in theme.css; the publish build (Vite lib mode) force-inlines all 60 KaTeX math fonts as data URIs, ballooning styles.css to 1.6MB (977KB gzip) and breaking the package's consumer-owns-fonts policy. Alias the katex stylesheet to an empty stub in vite.css.config.ts only — theme.css stays untouched (no rebase surface) and Plannotator's own apps, which import theme.css directly, still bundle KaTeX as before. Hosts that render math load katex.min.css themselves (bundler import, CDN tag, or self-hosted copy per HANDOFF.md), which also gets them lazy font loading. Verified: fresh build is 186.9KB / 30.8KB gzip with zero @font-face data URIs; consumer vite build CSS drops 1.66MB -> 200KB.
- Math rendering section: KaTeX css/fonts excluded from styles.css by design; three one-time host setup options (self-hosted recommended, CDN tag, bundler import) - styles.css size claim corrected (~187KB / ~31KB gzip) + strict-TS guarantee documented (verified against a standalone consumer) - AI-avoidability claim made precise: configure.ts statically imports useAIChat for its setter; unused AI code tree-shakes to zero (bundle- verified) — the runtime claim holds, the static wording was wrong - Loud warning on the loadSettingsFromBackend ordering footgun: configuring before hydration seeds generated defaults into the host backend and nothing re-runs hydration - DraftTransport.load() tombstone-generation contract spelled out - Seam-type barrel documented on the configure row; 'everything is importable' softened (some components/*.ts don't resolve via the *.tsx wildcard); stale diff stats refreshed
…dler-import option
The configStore resolved all settings eagerly in its constructor, at module import — before a host's configurePlannotatorUI() could install its StorageBackend — writing 17 plannotator-* cookies (including a generated identity) onto the host origin. Resolution now runs lazily on first settings access (get/set/init/loadFromBackend): by then the host backend is live, so the initial reads AND default-seeding writes route through it. A configured host gets zero cookies, ever. Plannotator unchanged: same resolution, same cookie seeding, same values — on first settings read (same page load) instead of at import. New configStore.lazyInit.seam.test.ts proves the contract from a fresh module graph; full suite + consumer strict tsc green.
1976428 to
184b24d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Make the document UI reusable as published building blocks
Extracts Plannotator's document UI (
@plannotator/ui) into installable, host-overridable building blocks so a separate app (the commercial Workspaces/Enterprise app) can reuse the rendering, theme, editor, settings, comments, and layout — without changing Plannotator's own behavior at all.THE LAW (how to read this diff)
Every change is move + decouple, never rewrite. For each spot where a shared component called a hard-coded
/api/*route or a Plannotator-only global, the I/O was lifted to an optional seam (a module-levelsetX()setter or an optional prop) whose default reproduces today's behavior exactly. Plannotator passes nothing and stays byte-for-byte identical; a host injects its own backend. Nothing Plannotator-specific was deleted.What's in scope
@plannotator/core(new): a small, browser-safe, zero-dependency package of pure utilities + types, carved out of@plannotator/sharedso@plannotator/uican be published without dragging the Node/git/server kitchen sink.@plannotator/sharedre-exports each moved module via one-line shims, so all ~99 internal import sites are untouched.@plannotator/ui: ~13 host-override seams across Phases 2–6 (image, storage, doc-preview, file-tree, identity, drafts, live comments, versions/diff, settings sync, save-to-notes, Ask AI), a singleconfigurePlannotatorUI()front door over the global seams, aloadFromBackend()settings-rehydration hook, and a precompiledstyles.cssbuild.Phases (each verify-gated, defaults = today's behavior)
@plannotator/core, complete the settings provider,configure()front door, precompiled CSS, publish-prepVerification
bun run typecheck— clean across all packages (core typechecks node-free).bun test— 1637 pass / 0 fail (main baseline 1620/0; delta is purely additive: new seam + override tests).madge— no circular dependencies (core ← shared,core ← ui,coredepends on nothing).git diffconfined topackages/{core,shared,ui,editor,ai}+ CI/config;packages/server,packages/review-editor,apps/hook,apps/opencode-plugin,apps/vscode-extensionuntouched.packages/uidepends only on@plannotator/core(non-test grep empty).NOT in this PR (deliberately gated)
core+uiand the first publish are gated on explicit go. (Publish flow:bun pm packthennpm publish *.tgz --provenance --access public,corefirst —bun pm packresolvesworkspace:*to the exact version, verified in the tarball.)9c1a96d9fixes an external-annotations stall (if the SSE stream errored once and the consumer toggled annotations off/on, the stale fallback flags meant neither SSE nor polling ever restarted; the flags now reset on re-enable). Strictly a recovery improvement; flagged here so it's approved explicitly rather than riding along under "seams only".How to review
feat(core): carve @plannotator/core …and follow the Phase 7feat(ui)/fix(ui)commits.packages/shared/*should each be a singleexport * from '@plannotator/core/X'.config/storage/workspace-status) import their types fromcoreand keep their Node impl — types live once./api/*fetch verbatim.main(picks up Bug: deleted review annotations reappear after refresh #948 + the 0.21.1 bump); Bug: deleted review annotations reappear after refresh #948's code-draft tombstone is reconciled with the draft-transport seam.Manual parity checklist (regression sweep — "nothing changed")
Build the real binary:
bun run --cwd apps/review build && bun run build:hook && bun build apps/hook/server/index.ts --compile --outfile ~/.local/bin/plannotatorMoved-module spot-checks: code-file link hover/click · share-URL round-trip · deny feedback text · code-review file tree + agent jobs/tour · annotate a folder · agent terminal ·
/goalflow · doc badges + favicon · live external comment.Full flows: plan-review (render → comment + redline → image → deny → diff badge → version browser → settings persist on reload → Ask AI → approve) · code-review (diff → annotate → expand → send) · annotate (
.md/ URL /.html).Watch closely: annotation highlights + plan-diff colors render (relocated CSS); settings persist across reload.
Reference
See
packages/ui/README.md— what@plannotator/ui+@plannotator/coreare, the host-override seams (configurePlannotatorUI), how a consumer installs/builds, and the one rule (don't reimplement from scratch — add a seam).