Repository navigation
Conversation
|
🤖 Review round 1: Princess Donut, reviewing exact head Verdict: 8/10 with one blocker. Scope is clean and the slice works on top of #2762. Scope matches the slice. Compared with the combined #2761 head Works on top of #2762 alone. The guide section, author type test and installed-document journey use only the editor plus #2762's Markdown and workspace surfaces. The shared composer changes don't alter existing behavior: the formatting tools only gain an optional always-open mode, and the link dialog keeps its "message" wording by default. Verified:
Blocker1. The editor keeps showing edits the plugin didn't accept, so what the user sees and what gets saved can differ. (Severity: blocker, controlled-contract and data-loss defect)
How it fails: a plugin enforcing its byte limit by ignoring over-limit changes, a normal pattern for controlled inputs, shows text it will never save. The user presses Cmd/Ctrl+Enter and the older Fix:
Optional
|
|
🤖 Fixed the controlled-editor blocker at Declined rich edits restore the accepted Validation: both declined-change tests fail against The split proof remains |
|
🤖 Review round 2: Princess Donut, scoped to the round 1 blocker and the fix commit Verdict: 7/10. The original blocker is fixed, but the rollback adds a new blocker and a medium defect. All results below come from temporary component tests run at this head. The probe files are deleted and the worktree is clean. Round 1 blocker: fixed
Blocker: after a rollback the caret jumps to the end, so the next keystroke edits the wrong place(Severity: blocker, correctness and silent unintended edit) Reproduced: document "Intro paragraph.\n\nClosing line." with the caret after "Intro". The plugin declines a change that would make the document longer, standing in for a byte limit. Typing "Z" is correctly declined. Pressing Backspace then removes the final period of the document, and the plugin accepts that because it is shorter. The saved value becomes "Intro paragraph.\n\nClosing line". In a separate probe, typing after a declined character appended text at the end ("Hello! world?") instead of at the caret. Why it matters: this happens in exactly the byte-limit situation the fix exists for. A user at the limit who backspaces to make room deletes text at the end of the document, possibly off-screen, without noticing. Fix: when rolling back, keep the selection from before the declined change, clamped to the restored text, and keep focus. Add a test for decline, then Backspace or typing, that asserts the result is next to the original caret. Medium: a declined edit can trigger an unrequested second change with the whole document reformatted(Severity: medium; Ned specifically asked about second callbacks) Impact: the plugin has no way to tell this from a user edit. A normal plugin accepts it, so the document is marked dirty and its next save reformats syntax the user never touched. The new test asserts a single callback only for "Hello world", which round-trips exactly, so it can't catch this. Fix: suppress the change that echoes the restored content itself, for example by recording the restored draft's serialization. Don't compare against the raw Out of scope as agreed: the two round 1 nits. |
|
🤖 Addressed both rollback defects in Declined edits restore the host editor’s last accepted checkpoint, including selection and undo state. Restoration avoids an edit transaction, and programmatic synchronization does not notify the plugin, so equivalent Markdown spellings cannot cause a second normalized change. Five new cases fail against the prior adapter and pass with the repair. All 43 focused checks pass. The full temporary installed-module editor file passes in Chromium and WebKit (4 checks), including decline→Backspace at the original caret, exact saved content, and single callbacks/raw source for The new recombined tree |
|
🤖 Review round 3: Princess Donut, scoped to the round 2 findings and the fix commit Verdict: 9/10, no blockers. Both round 2 findings are fixed, and I found no new defects. I checked with temporary component tests at this head; the probe files are deleted and the worktree is clean. Round 2 blocker (caret after rollback): fixedThe editor now restores its last accepted state using the engine's existing checkpoint, which covers the document, selection and undo history. The caret no longer moves.
Round 2 medium (second change callback): fixedRestoring no longer goes through an edit transaction, and echoes during the sync are suppressed.
Do the new tests catch regressions? YesWith only the adapter reverted to Defects introduced: none found
Optional
|
bd003ea to
2df7cd4
Compare
8027f49 to
c8129c6
Compare
|
🤖 Conflict-only rebase onto #2762 at |
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 Two findings: rich edits can change persisted Markdown paragraph semantics, and the unconditional bracket shortcut prevents literal bracket entry.
c8129c6 to
03617c5
Compare
|
🤖 Addressed both review findings in
The paragraph/break probes failed before the fix, and both literal-bracket cases failed before the destination guard. Adapter/composer files pass 82 checks; the editor component file passes 21. The full installed-plugin browser file passes 34 Chromium/WebKit checks, including native Enter/Shift+Enter and reader output. An additional temporary installed-module probe passes in both engines for literal brackets and rich/read structure. No permanent browser cases were added or removed; the existing document journey gained assertions. Browser checks and these captures were made at Built app with an installed example, disposable data and modeled relay/native inventory. Wide-viewport captures show the workspace pane; narrow uses enlarged text. Native packaged/live-relay and human acceptance remain pending. New-head CI and review remain pending.
|
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 Three document serialization defects remain: hard breaks before inline code, clipboard structure loss, and Shift+Enter after a typed backslash.
|
🤖 @kalvinnchau Fixed all three document serialization issues at
Validation: four inline-code regressions, three slash regressions and seven of eight clipboard structure cases fail before their fixes. The four complete affected unit files pass 114 checks. The final-head installed-plugin file passes all 34 Chromium/WebKit checks, including page-local clipboard events, reader structure, inline paste formatting and native typed Shift+Enter. The current-head capture journey passes in both engines (2 checks). Mandatory hooks pass. No permanent browser cases were added or removed; the existing installed-document journey gained assertions and component/conversion tests own the matrix. The browser clipboard checks use page-local ClipboardEvent/DataTransfer; the operating-system clipboard, packaged native build, live relay and human acceptance remain untested. New-head CI/review remain pending. Built app at
|
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 One document clipboard regression remains: unsupported multiline source is corrupted by both paste flavors.
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 Partial selections still corrupt raw task-list/table source in both clipboard flavors; full-selection round trips are repaired.
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 One new paste regression remains in paragraphs containing a hard break; the prior partial raw-source selection defect is repaired.
… an update (#3024) ## TL;DR A `buzz://plugin-install` Git link pinned to both a `commit` and a `path`, for a plugin already installed from that same repository and folder, now opens the **Update** review that a plugin's Git `requestInstall` already gets. If the link names another branch or tag than the plugin tracks, the review says so. Before, it hit the Add path's **Already installed** refusal, so there was no reviewed way to update from such a link. Links without both pins, and links to a plugin installed from another source, are still refused as duplicates. ## What changed - When the installed plugin was imported (not bundled), `PluginImport` treats a link with both `commit` and `path` like a bound `requestInstall` request when deciding between update and duplicate (`src/app/PluginImport.tsx`). It reuses the existing same-source check: the installed `updateSource` must be a Git source whose repository matches the fetched preview's source and whose folder matches the candidate's folder, and the candidate must have the installed manifest ID. Native import saves the requested `ref`, and later update checks follow it. So when a link's or plugin request's `ref` differs from the installed Git source's ref, the review adds "Future updates will follow <new> instead of <old>." An empty ref reads as "the repository default". - Confirming a requested update (link or plugin request) re-reads the live catalog and refuses with "The installed plugin changed. Close this review and check again." if the installed revision, enabled state or saved source changed after the review opened, so the review's "stays disabled" line can't go stale. - A requested update of a disabled plugin now says "This plugin will stay disabled.", as the Details **Update** review already does. Native install already keeps the enabled state, and the review offers no enable switch for updates. - Docs: `docs/plugin-architecture.md`, `docs/deep-links.md` and `docs/buzz-help.md` describe the pinned-link update. Protections that still apply: the commit pin (native refuses a ref whose tip differs, and the review re-checks the fetched commit), the folder filter, the access review against the installed manifest, the publisher line and native publisher-continuity refusal, the unsigned-to-signed rollback warning, explicit **Update** confirmation, and preview discard on cancel. Links with only `commit`, only `path`, or neither, and pinned links to a bundled plugin, keep showing **Already installed** / **Open in Buzz**. Not in scope: update discovery, Update-all, managed policy feeds or checkers (#2504, #2621, #637), signature or publisher-trust policy (#2212, #2746), and the compact request dialog (#2625). This PR doesn't touch their hunks. #2983 and #2762 also edit `tests/browser/plugin-import.spec.mjs`; this PR leaves that file unchanged. ## Supported operation On a Buzz desktop build that includes this change, open `buzz://plugin-install?repository=<repo>&ref=<ref>&path=<folder>&commit=<40-or-64-hex>` for a plugin whose installed copy came from a Git import of that same repository and folder. The review shows **Update <name>**, with access changes listed against the installed version, and states any change to the branch or tag later updates follow. Nothing changes until you choose **Update**. Native import still refuses the update if the ref's tip isn't the pinned commit, or if the publisher changed or a signed plugin became unsigned. ## Tests New `src/app/PluginImport.test.tsx` cases, all with fictional `example.com` fixtures: - A pinned link to the installed source reviews as **Update**, installs the pinned folder, and doesn't call enable. This is the regression test for the original failure. - A disabled plugin stays disabled and the review says so. - A pinned link with another ref than the installed plugin tracks (a named ref, or the repository default) reviews as **Update**, says which ref future updates will follow, and installs on **Update**. A same-ref link shows no such line. - A link without both pins (none, path only, commit only), or a pinned link to a bundled plugin, still shows **Already installed** with **Open in Buzz**. - An installed source in another repository or folder, a local folder, or an unknown source is refused with the source-mismatch message and **Open in Buzz**. - When only another plugin ID is installed from that source, the link offers a new install. - A fetched commit that differs from the pin offers no **Update**. - Cancel discards the preview without installing. - If the installed revision, enabled state or source changes after the review opens, **Update** is refused, for both a link and a plugin's `requestInstall`. The request and link harnesses now return a ready catalog from `plugins.snapshot()`, which the new confirm-time check reads. Checked on a Linux box at `37cf73e1`: `biome check` on both files, `pnpm typecheck`, and `vitest run src/app/PluginImport.test.tsx` (100 passed). After the first review fixes, the same three checks passed locally (102 tests), and the bundled-plugin test fails on the earlier predicate. The later enabled-state drift case (two lines plus one test input) passed biome locally; host load refused a local test run, so PR CI covers it. With only the `requested` line reverted, 6 of the 8 `pinned link` tests fail. Against `main`'s `PluginImport.tsx`, the same 6 fail and the other 94 pass. The two that pass either way are the existing pin-retry test and the other-plugin-ID new-install case, which should behave the same before and after. After review feedback, the tracked-ref disclosure was verified on a Blox box at `e913727f`: `biome check`, `pnpm typecheck` and `vitest run src/app/PluginImport.test.tsx` (103 passed). With the review line disabled, the new tracked-ref test fails. The before/after screenshots predate this line and show a same-ref update. ### Before / after These screenshots come from a temporary Chromium journey using the existing `nativeImports` IPC fixture and fictional `example` data; the journey is not committed. It installs `example.linked` from `https://github.com/example/plugins` at `plugins/linked`, then delivers the same pinned link again with a newer candidate. | | Light | Dark | |---|---|---| | Before (`main`) |  |  | | After |  |  | ## Remaining gaps - No native (Tauri) run: the Playwright screenshots use the existing IPC fixture. Native import, publisher continuity and the deep-link handoff were not changed here. - No human has tried it in the app yet, so `buzz-review-completed` is not set. ## Initiative context Status: https://g2.sqprod.co/apps/kylez-buzz-improvements-status (buzz-improvements T027). Fixes NEXUS-4327 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Kyle Zemek <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
d5e4786 to
7082669
Compare
ed63a40 to
416b9ba
Compare
…ments (#3322) ## TL;DR Plugins can now register a headless composer annotator with `ctx.conversation.registerAnnotator`. The host gives it the focused draft, validates the ranges it publishes, draws the marks, and replaces text only when the person picks one of the offered replacements, as one undo step. Checking, language, scheduling, timeouts and wording all stay in the plugin, so a spelling, grammar or style checker can ship as a standalone plugin. ## API ```ts ctx.conversation.registerAnnotator({ id, title, component }); // component: ComponentType<CompletionContext & { // observation: { revision, text, prose: { start, end }[] }; // frozen, one per document // publish(annotations: ComposerAnnotation[]): (() => void) | false; // }> // ComposerAnnotation = { start, end, text, label, kind?: "spelling" | "grammar", replacements?: { text, label? }[] } ``` The types are exported from `@buzz/author`. The full contract is in `docs/plugin-architecture.md` under "Composer annotators". **Who owns what.** | Host | Plugin | |---|---| | Mounting per focused composer, destination and enablement; revoking `publish` on change, removal or render failure | What to check, language, ignore rules, tokenization | | Draft projection, prose ranges and UTF-16 mapping | Debounce, timeouts, retries, failure policy | | Validating revision, expected text, prose containment, grapheme boundaries, labels and replacements; caps | Results, labels, replacement choice and ranking | | Underline, `aria-invalid`, right-click / ⌘. menu, focus return, one-step Undo, IME and read-only refusal | — | **Disclosure.** An annotator sees what completion providers already see, under the same eligibility (focused, enabled, outbox-capable composer), plus the prose ranges. It never sees the caret, and no observation is offered while unfocused, disabled, read-only or composing. **Publishing.** `publish` is bound to one observation. It returns `false` once the draft changed, the destination or enablement changed, the annotator was removed or failed to render, or the value is not an array. Otherwise it replaces that annotator's marks and returns a disposer that withdraws only them. A disposer for an earlier draft leaves the mapped underlines in place until the next publication, so per-observation cleanup does not flicker. The menu closes when its annotation is withdrawn or replaced, even for the same draft. **Validation and bounds.** - An annotation must match its text exactly, lie inside one prose range, not split a surrogate pair or grapheme cluster, and carry a 1–120 unit label. - Replacements: at most 5, distinct, at most 64 units, different from the marked text. An empty replacement deletes the range and needs a label. - No control, bidi or line-separator characters and no lone surrogates. - At most 200 annotations per publication and 400 marks per editor; earlier annotators win overlaps; at most one redraw per animation frame. **Applying a replacement.** The host rechecks revision, range, text, editability and composition; refuses mixed formatting; keeps the range's formatting; and closes history on both sides so typing, the replacement and further typing undo as three steps. ## Review notes - Adds an extension point to three `FOUNDATION` files: `conversation/contracts.ts`, `conversation/service.tsx` and `plugins/author.ts`. The change is additive and needs FOUNDATION review. - The native spellcheck default and autocorrection preference are unchanged. - Related open work this does not overlap: native spellcheck default (#2201, #2546), a generic draft read/replace API (#2656), other composer work (#2983, #3312). Other open PRs touching the same composer files: #2988, #3260, #3257, #2896. ## Tests - `composer-annotations.test.ts` (Node): prose-range exclusions, same-text revisions, range/grapheme/label/replacement validation and caps, overlap and merge, mark-preserving replacement and deletion with Undo/Redo, stale and mixed-format refusal, composition mapping, the hidden mark on the word being typed. - `ComposerAnnotators.test.tsx` (jsdom, annotator registered through the real `PluginRuntime` and `ConversationService`): observation shape and context, emoji offsets, keyboard and right-click menus, Escape and focus return, replacement with Undo/Redo, `aria-invalid` kinds, stale and invalid publications, disposer ownership, menu closing on same-draft withdrawal or replacement, earlier underlines kept across a stale disposer, an immediately publishing annotator surviving a destination change, same-text structural change, retirement on disable, destination change, plugin removal and render failure, IME composition, disabled and read-only editors, unmount. - `MessageComposer.test.tsx`: the real composer wiring, including retirement on a destination change. - No browser journey was added; jsdom covers the contract and the remaining gaps are native-only. On a Linux remote box against this head rebased on current `main`: `pnpm check` (Biome, `tsc`, design typecheck and design checks) passed, and `vitest run` over `src/features/conversation`, `src/plugins`, `EditableInput`, `MessageComposer` and `composer-annotations` passed (35 files, 1374 tests). The four new lifecycle tests fail against the previous source and pass with the fix. The local pre-push hook was skipped because CI runs the same checks. **Not yet verified:** - right-click selection and context-menu behavior in native WebKit; - whether ⌘. reaches the webview on macOS; - screen-reader announcement of `aria-invalid`; - real IME composition in the desktop app; - both themes, narrow and wide widths, and enlarged text, with a real annotator installed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Kyle Zemek <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
Reuse the host editing engine, prototype Markdown adapter, persistent formatting and generic link destinations through conversation.ui.DocumentEditor. Keep content, source mode, link intent and save policy controlled by the plugin. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Keep rich text synchronized with the accepted controlled value, and rebuild it from accepted source content instead of source keystrokes. Suppress rollback echoes through onChange and cover declined rich/source edits plus accepted source transitions. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Restore the last accepted editor checkpoint so a declined edit retains selection and undo state without a normalized change callback. Cover a following Backspace and equivalent italic, bold, bullet and numbered Markdown forms. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Use opt-in CommonMark serialization for documents, retaining nested paragraph/list structure and marked hard breaks. Keep chat defaults and align document Enter, Shift+Enter and serialized editing budgets with the reader. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Leave literal double brackets editable when there are no destinations, while preserving the existing destination shortcut. Cover omitted and empty choices plus the opt-in path. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Route prose before code spans through the hard-break-aware leaf. Cover root, quote and list round trips. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Escape an odd authored trailing slash before the generated hard-break marker. Cover slash runs and typed Shift+Enter without changing chat output. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Use document policy for wire output, editing budgets and clipboard import/export. Retain hard breaks and list spacing in owned HTML, parse plain Markdown through the existing bounded adapter, and keep default chat clipboard output unchanged. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Retain embedded fallback source newlines during plain paste and carry them through owned HTML with the existing source-span codec. Keep supported prose normalization and default chat behavior unchanged; cover task lists, tables and supported prose in both clipboard flavors. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Use the raw destination context before interpreting copied fragments as document blocks. Keep task-list and table source intact through both clipboard flavors, with partial-selection regressions beside the existing full-selection cases. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Inspect text children for raw-source newlines so document prose with hard breaks keeps structured paste and URL-over-selection behavior. Cover both clipboard flavors in hard-break and ordinary prose destinations while retaining the raw-source regressions. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Cover authored clipboard output in chat and document modes after integrating namesake send qualification. Keep written names and explicit qualifiers literal in document source without signed recipients. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Exercise the shared paste path with office text and its image sidecar in the document editor, alongside main's existing composer and clipboard classification coverage. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
416b9ba to
a88e640
Compare
7082669 to
9e00e26
Compare
…itor Carry the shared Markdown table-sizing repair and updated thread wrapping checks into the editor stack. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
…itor Carry the shared Markdown bounds and keyboard-access fixes while retaining the editor fixture and its editing assertions. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
















Overview
Category: improvement
User Impact: Installed document plugins can offer rich-text and Markdown-source editing while preserving paragraphs, hard breaks and clipboard content.
Problem: Plugins need editing without copying app code, while chat serialization and clipboard rules can change a document’s meaning when reused directly.
Solution: Expose a controlled
conversation.ui.DocumentEditorusing the existing input engine, formatting controls and link dialog with document policy. Plugins own persistence, permissions and mode controls; leavingvalueunchanged restores a declined edit and caret without another change notification. Read mode uses the parent PR’s bounded, keyboard-accessible renderer with document-scale headings, wrapping inline pills and filled, wrapped code blocks capped at a partial bottom line.This PR is stacked on #2762 and should merge after it. The formatting bar remains formatting-only; the example’s Read/source controls are plugin-owned and sit beside it. Mainline namesake-send qualification, authored-only copying, literal folder paths and office/long-paste behavior are preserved alongside document mode and raw-source/hard-break handling.
Changes
File changes
docs/plugin-authoring-guide.md
Document controlled editing, CommonMark/clipboard policy, optional destinations, limits and plugin-owned saving.
src/features/conversation/service.test.tsx
Verify the injected editor is the existing host component.
src/features/conversation/service.tsx
Expose the existing controlled DocumentEditor through conversation UI injection.
src/features/messages/ComposerFormattingTools.tsx
Reuse persistent formatting controls for document editing.
src/features/messages/ComposerLinkDialog.tsx
Reuse validated links with optional loaded document destinations.
src/features/messages/DocumentEditor.module.css
Keep document-editor layout with its feature owner.
src/features/messages/DocumentEditor.test.tsx
Cover controlled acceptance/decline, caret/history, paragraph and break semantics, source preservation, paste and limits, including office text with an image sidecar.
src/features/messages/DocumentEditor.tsx
Keep the plugin value authoritative while selecting document policy for the existing rich/source input.
src/features/messages/EditableInput.tsx
Apply the selected Markdown policy to budgets and clipboard paths while distinguishing literal source newlines from semantic hard breaks.
src/features/messages/composer-clipboard.test.ts
Retain chat behavior and cover authored-only namesake copying in chat/document modes alongside mainline paste classification.
src/features/messages/composer-clipboard.ts
Export document Markdown and sanitized rich HTML while retaining authored-only copy and literal fallback source.
src/features/messages/composer-document.test.ts
Verify backward-compatible document list-spacing metadata.
src/features/messages/composer-document.ts
Retain document list spacing in the existing editor schema.
src/features/messages/composer-dom.ts
Distinguish document paragraphs from hard breaks in the existing DOM conversion.
src/features/messages/composer-markdown.ts
Preserve chat defaults and namesake send qualification while serializing document structure, literal folder paths and hard breaks.
src/features/messages/document-editor-markdown.test.ts
Compare parsed document meaning across nested structure, hard breaks, inline code, backslashes and bounded fallback source.
src/features/messages/document-editor-markdown.ts
Reuse the bounded Markdown parser and existing composer schema for supported document structure.
src/features/messages/selection-copy.test.tsx
Keep selection-copy behavior asserted with the schema defaults.
src/features/messages/selection-copy.ts
Carry only whitelisted document spacing metadata through sanitized clipboard import.
src/plugins/author.test.mjs
Compile an independent consumer of the public contracts and reject host-only workspace options.
src/plugins/author.ts
Export the supported document editor props, handle and selection types.
tests/browser/document-plugin.mjs
Exercise the injected host UI from an installed module that owns its content and saving.
tests/browser/plugin-import.spec.mjs
Extend the installed-document workflow through rich/source/read modes, formatting, clipboard flavors, links and controlled saving.
Reproduction steps
docs/plugin-authoring-guide.md. Switch between rich, Markdown-source and read modes; save with Cmd/Ctrl+Enter.valueunchanged inonChangeto decline an edit. The accepted document and caret restore; the next edit and save use that accepted state.Validation and limits
The parent now includes main
c1a21a2a99atfc5ed99691; the editor stack includes that parent at3e9f5b8493. The previous hosted merge combined two imports ofusePanelTabTitle, preventing Vite startup and producing Biome's redeclaration error and unused-import warning. The integration keeps one import and retains bothpanels.useTabTitleandpanels.ui.useTabTitle. Each current head's merge tree with this main equals its branch tree.Both integrated heads pass the full
pnpm checkcommand (lint, TypeScript and design checks). The parent also passes the production Vite build, including CI’s FI fixture configuration, and all 75 panel tests across 7 full files, including both tab-title APIs.The final editor head passes the complete installed-plugin and shared-Markdown browser files: 43 passes and 1 existing WebKit screenshot-only skip across Chromium/WebKit. These checks exercise installed documents, rich/source editing, code wrapping and the half-line scroll cue, exact copy-button traversal and native keyboard scrolling. No browser cases or assertions were added, removed or relaxed for this integration.
The normal atomic push gates pass on the final editor head: TypeScript and related unit tests, design checks, and publication-policy checks. The parent also has its separate lint/type/design, panel-unit and build evidence above. Hosted CI validates both published heads; its results are pending.
The renderer, inline-code CSS, code-fence component, panel workspace and document editor sources are unchanged from the preceding reviewed heads. The existing screenshots remain representative. Browser fixtures use real app components with disposable content and modeled relay/native inventory; packaged-native and live-relay acceptance were not repeated. Required reviewer approvals remain pending.
Screenshots
Headless built-app fixtures with disposable documents and modeled relay/native inventory. The rich/source example places plugin-owned mode controls beside the host’s formatting-only bar. Read mode uses the shared document heading scale and code treatment from #2762.