Skip to content

NEXUS-4029: Preserve semantic chips in the core composer - #2303

Merged
gilgtm merged 2 commits into
mainfrom
gil/nexus-4029-composer-tokens
Nov 11, 2026
Merged

gilgtm merged 2 commits into
mainfrom
gil/nexus-4029-composer-tokens

Conversation

@gilgtm

@gilgtm gilgtm commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds host-internal semantic chips to the composer (070ad3b96). The folder (#2789) and skill (#2790) layers build on them.

  • Chips: a chip is a token node with a bounded ComposerToken, a folder or a skill. Extra JSON fields round-trip without interpretation.
  • Editing: the editor inserts a chip over an open completion query as one undo step, names it for assistive technology, keeps it through saved drafts, and copies it as its label text. A decoration or link never adopts it.
  • Who can make one: only a provider that a bundled plugin registered. The plugin runtime marks the owner it creates for a host-shipped module, and completionResult keeps a token edit only for a marked entry. An id, revision, title or flag that a provider claims is not provenance.
  • Public API: the plugin completion contract, MentionDraft and the generated public declarations are unchanged.
  • Unsent in this layer: every composer keeps a draft with chips unchanged and refuses to send it. Saved chip data that does not restore as a chip restores no chip.

Testing

Local runs on macOS, on the stack rebased onto main 9596cd3db:

  • The pushed heads differ from the tested adbdf424e chain only in tests/browser/solo-rename.spec.mjs. 02f9c0277 makes the new plugin-owned lifecycle test wait for the chat's row before filing it, after the hosted flake check caught it racing the roster load. With that fix, the full solo-rename.spec.mjs passed 180/180 at 02f9c0277 (10 repeats, Chromium and WebKit) and 120/120 at c99d0ce51 (5 repeats).
  • At this PR's head eff434bd0: pnpm check (Biome, TypeScript and the design checks) and the icon check passed. Vitest for the files the flake check selects for this PR: 632 passed.
  • At the stack tip adbdf424e: full Vitest, 20556 passed and 23 skipped (includes browser-host/channel-kit.test.mjs).
  • Browser journeys at the stack tip, Chromium and WebKit: the full tests/browser/solo-rename.spec.mjs, repeated 5 times, passed 120/120. The 34 other specs that the flake check relates to the stack, run once in each engine: 343 passed, 3 skipped by their own conditions. The full browser suite was not run locally.
  • The Nexus plugin's connected browser harness ran on the actual Buzz web shell and plugin UI, with a simulated relay and generated identities, before the rebase. Full run at host pin 049a9b57e (now 1fbd49c49), which includes this PR: 53 passed.

Native testing: Gil ran the native desktop app on this stack at adbdf424e, with the matching Nexus plugin build. Gil confirmed that these native-only paths passed:

Agent testing did not reach these paths: the agent ran no native app, no real relay and no real agent.

Known gaps

  • None specific to this PR.

Hosted or local failures that this stack does not cause:

  • tests/browser/typing.spec.mjs:290 ("a growing channel/thread composer lifts history with it at the bottom") intermittently fails with React error Add complete reaction toggles to the message menu #185 on main too. Locally it failed 12 of 12 runs at 9596cd3db and 9 of 12 at a later main commit.
  • On macOS, cargo test -p Buzz crashes the library test binary (SIGSEGV), and some host_command deadline tests fail. Both happen the same way at base 9596cd3db, before this stack. Hosted Linux CI is the reference for these.

Test changes

Titles are compared only in the test files each range changes. Tests that moved or were renamed are matched by title.

Removed by this PR's diff (0)
  • none
Added by this PR's diff (10)
  • src/features/conversation/ComposerCompletions.test.tsx: hands a chip edit to the composer only from a bundled provider (bundled: %s)
  • src/features/conversation/ComposerCompletions.test.tsx: opens no query at a trigger inside a resolved chip
  • src/features/conversation/completion.test.ts: keeps chip edits only from a provider a bundled plugin registered, for that registration's lifetime
  • src/features/messages/MessageComposer.test.tsx: inserts a bundled provider's chip as one atom and keeps it unsent until chips ship
  • src/features/messages/composer-token.test.tsx: bounds token JSON by UTF-8 bytes, including multibyte identities
  • src/features/messages/composer-token.test.tsx: copies a bounded, frozen token of a known kind and nothing else
  • src/features/messages/composer-token.test.tsx: copies a chip as its label text, never as a link its label resembles
  • src/features/messages/composer-token.test.tsx: refuses an unknown kind, a stale or literal range, and edits beyond the chip or length bounds
  • src/features/messages/composer-token.test.tsx: replaces an open query with one chip: named, separate from recipients, one undo step, and restored from a saved draft
  • src/features/messages/composer-token.test.tsx: restores a chip only from chip data its snapshot keeps
In the previous version of this PR, not carried over (3)
  • src/features/messages/MessageComposer.test.tsx: inserts a bundled provider's chip as one atom and keeps it unsent until task references ship (task chat: %s)
  • src/features/messages/composer-token.test.tsx: refuses a workspace, a stale or literal range, and edits beyond the chip or length bounds
  • src/features/messages/composer-token.test.tsx: restores a chip, and marks chip data any snapshot rejection would drop as another build's data

buzz-review-completed

@gilgtm gilgtm changed the title gil/nexus 4029 composer tokens NEXUS-4022: Preserve structured composer tokens Nov 3, 2026
@gilgtm
gilgtm force-pushed the gil/nexus-4029-composer-tokens branch from d0d88cb to a10d7e1 Compare November 3, 2026 20:09
@gilgtm
gilgtm changed the base branch from main to gil/nexus-4064-copy-status November 3, 2026 20:09
@gilgtm
gilgtm added this pull request to stack #2307 November 3, 2026 20:09
@gilgtm
gilgtm force-pushed the gil/nexus-4029-composer-tokens branch from a10d7e1 to 32f3960 Compare November 4, 2026 21:15
@gilgtm gilgtm changed the title NEXUS-4022: Preserve structured composer tokens NEXUS-4022: Preserve Nexus metadata in existing composer tokens Nov 4, 2026
@block block deleted a comment from chatgpt-codex-connector Bot Nov 4, 2026
@gilgtm
gilgtm marked this pull request as ready for review November 5, 2026 03:09
@gilgtm
gilgtm requested a review from a team as a code owner November 5, 2026 03:09

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord automated source review via Wes's account, with independent Rocket, Mantis and Groot coverage.

P2 — Remove internal coordination links from the public PR description. The opening ELIE overview link points to an internal service, and the closing issue reference exposes an internal tracker/workspace identifier. This repository is public, so those URLs are published to readers who do not have access to the underlying material. Remove those links or replace them with a public summary/reference. The source code does not need to change for this finding.

No actionable implementation defect established in the reviewed diff and callers. Reviewed head 32f39609612ab66ceb84532d033fbffca9f1257c against base 662ccc546ee01d00e55ac9ef7317deb3494e880b; no attached images were present in the description.

Source-only: no tests, builds, app launches, native editing, accessibility or performance workflows executed. Downstream task consumers and human acceptance remain unverified. The hosted snapshot for this head reports CI required failing, including Chromium 2/9 and WebKit 3/14 browser jobs; their cause was not established by this review. This COMMENT review is not an approval or a runtime-readiness verdict.

@gilgtm
gilgtm force-pushed the gil/nexus-4029-composer-tokens branch from 32f3960 to a801533 Compare November 5, 2026 13:52
@gilgtm
gilgtm removed this pull request from stack #2307 November 5, 2026 13:54
@gilgtm
gilgtm changed the base branch from gil/nexus-4064-copy-status to main November 5, 2026 13:55
@gilgtm
gilgtm added this pull request to stack #2612 November 5, 2026 13:55

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord automated source follow-up via Wes’s account.

No new actionable findings. The previously reviewed 13-file composer-token feature patch is byte-identical; the only old-to-new endpoint change is an inherited CommunityAddress.test.tsx retry assertion, also identical to the merge-base delta. This uses the small-follow-up exception to the prior Rocket, Mantis, Groot and Star Lord review; no new team review is claimed.

The public-description links identified in the previous review remain present, so that requested cleanup is still unresolved. No duplicate inline findings added.

Reviewed head a80153355a3b63008f08d9bba429051a5836fcbf against base 9c941525cd37ba1ead6d30547524f577484c00a1 (merge base 27c60c28fc7d0fb5771d640bb22ec90b2877b916). Public description and sole feature commit inspected; no attached images.

Source-only: no tests, builds or app execution; native editing, clipboard/focus/accessibility behavior, downstream consumers and human acceptance remain unverified. Hosted snapshot reports CI required and WebKit 14/14 failing; cause not established. This COMMENT is not approval or a runtime-readiness verdict.

kazemek21 added a commit that referenced this pull request Nov 5, 2026
…a channel (#2512)

## TL;DR

Adds `conversation.ui.Conversation`, a public host-owned scope that lets
a plugin's own `ui.Message` rows and `ui.Composer` for one channel get
native Edit, confirmed Delete with recovery and retry, and host media
review. Mounting it never marks anything read, follows a thread or
enters the channel visit. Cross-channel overview cards need this; today
a bare `ui.Message` row has neither Edit nor Delete, and `ui.Thread`
always visits.

Fixes NEXUS-4193

## How it works

`ui.Conversation({ session, scope, channelId, channelName?, threadId?,
children(interactions) })` renders the existing `ConversationChannel` in
its host-dialog, non-visiting mode (`host` plus `active={false}`). Inbox
Drafts already uses this mode internally. Inside it:

- `MessageManagement` supplies the row menu's Edit/Delete under the
existing permission checks, the delete confirmation and the
failed/unconfirmed recovery notice.
- `MessageEditScope` makes Edit target the `ui.Composer` in the same
scope. A scope without a composer offers no Edit, as in other previews.
- `ConversationHost` owns media review and panel overlays, focus return,
and the existing stale-session guard (nothing opens once the relay
snapshot is no longer this ready session).

`children` receives the host's `onOpenLink`, `canOpenLink` and
`onOpenMediaReview` to pass to the rows. `EmbeddedThread` and the new
scope now share one `HostedChannel` wrapper, so `ui.Thread` behavior is
unchanged. No changes to `MessageManagement`, `ConversationHost`,
`MessageComposer` or `contracts.ts`.

## Evidence

`src/features/conversation/EmbeddedConversation.test.tsx` mounts two
fictional channel cards (real relay session, real `ui.Message` rows and
`ui.Composer` from `ConversationService`; only `MediaReviewViewer` is
reduced, since it has its own suites). It covers:

- the same rows without the scope have no Edit/Delete; inside it, each
card offers both
- mount, background presentation, re-present and unmount never call
`enterChannel`, `leaveChannel`, `follow`, `markMessageRead`,
`markChannelRead` or the roster's `profiles.reserve`, and publish
nothing
- Edit fills only that card's composer and publishes a kind-40003 edit
tagged with that card's channel. The other card's message is untouched.
- Delete: cancel publishes nothing; confirm publishes kind 5; a relay
rejection shows the recovery notice in that card only; Retry resends the
same event id and the row goes away
- media review opens with the card's channel and message, and stops
opening after the session stops being ready

Fail-then-pass: with `active={false}` removed from the scope, all 4
tests fail (`enterChannel` and `profiles.reserve` are called). The scope
restored, all pass.

The packed `@buzz/author` consumer fixture
(`tests/fixtures/conversation-consumer`) now renders its rows and
composer inside `ui.Conversation` and passes `interactions.onOpenLink`
to its rows. So the generated declarations must export the new component
and its `children(interactions)` type, and the existing
`conversation.spec.mjs` journey mounts it in the real app. The fixture
type-checks against freshly built declarations. As a negative control, a
misspelled interactions field fails `tsc`.

Run on a Linux Blox box on base `a34ba4f` (the local host-build guard
refused under load):
- `vitest run src/features/conversation
src/features/messages/MessageManagement.test.tsx
src/bundled/inbox/DraftsView.test.tsx`: 19 files, 221 tests passed
- `tsc --noEmit` and `biome check --error-on-warnings` on the touched
files: clean
- `pnpm author:build`, packed and installed into a copy of the consumer
fixture, then `pnpm build` (`tsc`): clean

No browser journeys were added or removed. The existing consumer journey
now mounts the scope; it runs only in hosted CI, not locally. Focus,
overlay and native-edit geometry are unchanged existing
`ConversationHost`/composer behavior that existing suites already cover.

## Limits and coordination

- Not exercised in the native desktop app or a browser journey. Hosted
CI runs the full suites.
- Two `ui.Conversation`s for the same channel each show that channel's
recovery notice, because recovery is per channel. The docs say to render
one per channel or card.
- No overlap in hunks or behavior with #2483 (Solo list arrangement/row
action in `service.tsx`), #1889, #2306 or #2303 (`contracts.ts`), #2083
(`MessageManagement.tsx`) or #2167 (`ConversationHost.tsx`). This PR
does not edit `contracts.ts`, `MessageManagement.tsx` or
`ConversationHost.tsx`.
- This PR doesn't adopt the scope in any plugin. That is separate work.

## Initiative context

[Buzz Threads
status](https://g2.sqprod.co/apps/kylez-buzz-threads-status)

🤖 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]>
@gilgtm
gilgtm removed this pull request from stack #2612 November 6, 2026 04:28
@gilgtm
gilgtm force-pushed the gil/nexus-4029-composer-tokens branch from a801533 to 7bc1a52 Compare November 6, 2026 04:29
@dr0uleau
dr0uleau requested a review from a team November 6, 2026 04:48
@gilgtm
gilgtm changed the base branch from main to gil/nexus-4156-workspace-lead November 6, 2026 12:02
@gilgtm gilgtm changed the title NEXUS-4022: Preserve Nexus metadata in existing composer tokens NEXUS-4029: Preserve semantic chips in the core composer Nov 6, 2026
@gilgtm
gilgtm added this pull request to stack #2791 November 6, 2026 12:04
@gilgtm
gilgtm force-pushed the gil/nexus-4029-composer-tokens branch from 7bc1a52 to fc50baa Compare November 6, 2026 16:02
@tellaho

tellaho commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

🤖 Could we hold this semantic-chip layer with the task-chat work in #2784 and #2785 until after the Nov 10 event? It supports that new core experience, so the current UI/UX frost applies to the stack as a whole. Design can revisit the extension approach after the event.

@gilgtm
gilgtm force-pushed the gil/nexus-4029-composer-tokens branch from fc50baa to 2c3e8c1 Compare November 10, 2026 03:00
@gilgtm
gilgtm marked this pull request as draft November 10, 2026 03:01
@gilgtm
gilgtm removed this pull request from stack #2791 November 10, 2026 03:03
@gilgtm
gilgtm added this pull request to stack #3176 November 10, 2026 03:03
@gilgtm
gilgtm force-pushed the gil/nexus-4029-composer-tokens branch from 2c3e8c1 to eff434b Compare November 10, 2026 05:31
@gilgtm
gilgtm force-pushed the gil/nexus-4029-composer-tokens branch from eff434b to 070ad3b Compare November 10, 2026 06:17
@gilgtm
gilgtm marked this pull request as ready for review November 10, 2026 06:37

@dr0uleau dr0uleau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 approved at 070ad3b -- the current review has no P2 or worse defects and CI passes

@gilgtm
gilgtm force-pushed the gil/nexus-4029-composer-tokens branch from 070ad3b to 51f981b Compare November 11, 2026 01:16
@gilgtm
gilgtm marked this pull request as draft November 11, 2026 01:16
@gilgtm
gilgtm force-pushed the gil/nexus-4029-composer-tokens branch from 51f981b to d6af1fb Compare November 11, 2026 03:18
@gilgtm
gilgtm force-pushed the gil/nexus-4029-composer-tokens branch from d6af1fb to 771af41 Compare November 11, 2026 03:23
@gilgtm
gilgtm marked this pull request as ready for review November 11, 2026 06:23
Base automatically changed from gil/nexus-4156-workspace-lead to main November 11, 2026 15:22
gilgtm and others added 2 commits November 11, 2026 09:22
Add the core chip editor infrastructure that the folder and skill layers
build on. A semantic chip is a token node with a bounded
`ComposerToken`: a `folder` or a `skill`, plus extra JSON fields that
round-trip without interpretation. The editor inserts one over an open
completion query as one undo step, names it for assistive technology,
keeps it through saved drafts, copies it as its label text, and never
lets a decoration or link adopt it.

Only a provider that a bundled plugin registered can produce a chip.
The plugin runtime marks the owner object it creates for a host-shipped
module, and contribution registries mark the entries such an owner
registers. completionResult keeps a token edit only for a marked entry.
An id, revision, title or flag that a provider or a ctx.extend scope
claims is not provenance. The token edit lives in host-internal types in
completion.ts. The plugin completion contract, MentionDraft and the
generated public declarations are unchanged.

Chips stay unsent in this layer. Every composer keeps a draft with chips
unchanged and refuses to send it. Saved chip data that does not restore
as a chip restores no chip, whatever rejected the snapshot: an
unreadable token, a label that is not its source, a recipient, link or
pending-mention conflict, or any other invalid part of the document.

Co-authored-by: Codex <[email protected]>
Co-authored-by: Claude <[email protected]>
Signed-off-by: Gil Birman <[email protected]>
A folder chip now writes the folder's path, and a skill chip writes
`$name`. Both still show only their label. The folder chip uses the
shared inline-chip classes.

Co-authored-by: Claude <[email protected]>
Signed-off-by: Gil Birman <[email protected]>
@gilgtm
gilgtm force-pushed the gil/nexus-4029-composer-tokens branch from dc29a8a to 76a511c Compare November 11, 2026 15:22
@gilgtm
gilgtm merged commit 5df7dd9 into main Nov 11, 2026
176 of 180 checks passed
@gilgtm
gilgtm deleted the gil/nexus-4029-composer-tokens branch November 11, 2026 17:38
bostonaholic added a commit that referenced this pull request Nov 11, 2026
…y-untagged-reactions

* origin/main: (324 commits)
  fix(github): show private-repo body images from signed links (#3252)
  NEXUS-4029: Preserve semantic chips in the core composer (#2303)
  Make plugin signature enforcement opt-in (#2212)
  NEXUS-4156: Add workspace Leads and choose a chat's workspace (#2788)
  feat(plugins): report conclusively retired update providers (#3263)
  feat(messages): let attachment renderers open files from the host file card (#2239)
  feat(charts): bundle a Charts code-block renderer (#3226)
  Expose native reply grouping through Conversation.ui (#3245)
  NEXUS-4163: Keep plugin-owned chat deletion in Solo (#2787)
  NEXUS-4156: Expose Solo Workspaces to plugin regions (#2786)
  NEXUS-4022: Start region chats in the New Chat editor (#2784)
  feat(link-previews): unfurl web links below messages by default (#3225)
  fix(messages): keep names in literal Markdown as written when editing (#2834)
  feat(plugins): fetch a packaged managed plugin policy from a URL (#2504)
  fix(goose): enable deferred tool loading for goose agents (#3227)
  fix(unread): preserve manual intent in captured explicit reads (#3228)
  NEXUS-4337: Expose retained plugin update reviews (#3075)
  feat(conversation): add a link preview slot below messages (#3219)
  feat(plugins): let link previews read a public page's preview fields (#3217)
  Let sidebar row plugins decorate ordinary Solo chats (#3222)
  ...
tlongwell-block added a commit that referenced this pull request Nov 11, 2026
* origin/main: (121 commits)
  Expose Solo region state before a component mounts (#3296)
  fix(builderlab): say a 401 was refused, not expired, and show each sign-in failure once (#3241)
  feat(agents): file new Solo chats in explicit personal workspaces (#2857)
  perf(css): limit broad style invalidation (#3267)
  Delay and fade in the Bestie badge celebration (#3292)
  fix(github): show private-repo body images from signed links (#3252)
  NEXUS-4029: Preserve semantic chips in the core composer (#2303)
  Make plugin signature enforcement opt-in (#2212)
  NEXUS-4156: Add workspace Leads and choose a chat's workspace (#2788)
  feat(plugins): report conclusively retired update providers (#3263)
  feat(messages): let attachment renderers open files from the host file card (#2239)
  feat(charts): bundle a Charts code-block renderer (#3226)
  Expose native reply grouping through Conversation.ui (#3245)
  NEXUS-4163: Keep plugin-owned chat deletion in Solo (#2787)
  NEXUS-4156: Expose Solo Workspaces to plugin regions (#2786)
  NEXUS-4022: Start region chats in the New Chat editor (#2784)
  feat(link-previews): unfurl web links below messages by default (#3225)
  fix(messages): keep names in literal Markdown as written when editing (#2834)
  feat(plugins): fetch a packaged managed plugin policy from a URL (#2504)
  fix(goose): enable deferred tool loading for goose agents (#3227)
  ...

Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
joahg added a commit to joahg/buzz-app that referenced this pull request Nov 11, 2026
* origin/main:
  NEXUS-4029: Offer folder chips in the composer (block#2789)
  perf(css): narrow effort button invalidation (block#3297)
  NEXUS-4433: Expose saved plugin installation source kind (block#3242)
  fix(identity): keep an unbacked key safe on Replace and unblock a corrupt saved key (block#3233)
  Expose Solo region state before a component mounts (block#3296)
  fix(builderlab): say a 401 was refused, not expired, and show each sign-in failure once (block#3241)
  feat(agents): file new Solo chats in explicit personal workspaces (block#2857)
  perf(css): limit broad style invalidation (block#3267)
  Delay and fade in the Bestie badge celebration (block#3292)
  fix(github): show private-repo body images from signed links (block#3252)
  NEXUS-4029: Preserve semantic chips in the core composer (block#2303)
  Make plugin signature enforcement opt-in (block#2212)
  NEXUS-4156: Add workspace Leads and choose a chat's workspace (block#2788)
  feat(plugins): report conclusively retired update providers (block#3263)
  feat(messages): let attachment renderers open files from the host file card (block#2239)
  feat(charts): bundle a Charts code-block renderer (block#3226)
  Expose native reply grouping through Conversation.ui (block#3245)

Signed-off-by: Joah Gerstenberg <[email protected]>

# Conflicts:
#	src/features/conversation/ComposerCompletions.test.tsx
#	src/features/conversation/ComposerCompletions.tsx
#	src/features/messages/MessageComposer.tsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants