Skip to content

feat(operator): write by default, editable LLM configs behind a gate guard, docs access, and a header launcher - #137

Merged
ginccc merged 4 commits into
mainfrom
feat/operator-write-by-default
Aug 11, 2026
Merged

feat(operator): write by default, editable LLM configs behind a gate guard, docs access, and a header launcher#137
ginccc merged 4 commits into
mainfrom
feat/operator-write-by-default

Conversation

@ginccc

@ginccc ginccc commented Aug 11, 2026

Copy link
Copy Markdown
Member

Makes the Platform Operator actually able to operate the platform, moves its launcher somewhere sensible, and standardises the default model. Pairs with labsai/EDDI#668 (docs-for-agents, MCP resource bridge, strict task-level toolApprovals) — this PR works against today's backend and does not depend on that one merging.

1. Write by default

read_write is now the default scope, freely selectable on first activation; read-only is the explicit opt-down.

The old flow demanded a two-step bootstrap — activate read-only, come back, reconfigure once a gate had been verified — because isWriteScopeAvailable gated the offer on gate.verified, which cannot exist before an operator does. That precondition was weaker than what activation already proves about the agent it is actually creating:

  • verifyGateInstalled reads the gate back from the just-provisioned document, and
  • the write canary provokes a real gated write and rolls the entire activation back (undeploy, delete, clear the config variable) on anything but a clean pause.

So a first-activation write grant cannot survive a broken gate — it is refused, not risked. isWriteScopeAvailable now only checks that WRITE_ENDPOINTS is non-empty.

Caller-identity is no longer demanded either: on an OIDC deployment authMode: none cannot activate at all (the form blocks it — tool calls would 401), and on a no-auth deployment there are no identities to attribute approvals to. Requiring it made write scope permanently unreachable exactly where EDDI gets evaluated.

2. LLM configs are editable — behind a hard gate guard

llmstore writes are granted, so the operator can finally edit an agent's system prompt and model. That was the single largest capability gap: previously it could change rules, outputs, slot-filling and tool wiring, but not what the agent actually says.

That document is also the one writable store that can carry a gate — LlmConfiguration.Task.toolApprovals fully replaces the agent-level hitlConfig.toolApprovals (LlmTask.java). So the grant is valid only alongside the new gate-guard.ts, which is a control rather than a warning (it disables Approve outright, same class as self-guard.ts):

  • any llmstore write whose body carries toolApprovals at any depth is refused;
  • so is any whose body is truncated or unparseable — "we did not see it" is not "it is not there", and an approver who cannot see the whole document cannot meaningfully approve it.

Absence is safe, not merely unchecked. A body without toolApprovals leaves the field null, so the agent-level gate applies. Falling back can only leave a task as protected as its own agent already was; an agent with no gate was already ungated. Combined with self-guard.ts (which blocks the self-targeted updateResourceUri that is the only way to make an llm edit take effect on itself), the self-ungating loop stays closed.

blocked-calls.ts now resolves both guards in one place — the three approval surfaces were each inlining findSelfTargetedCalls plus a copy of its i18n string, and a second guard would have triplicated that. A control that fires on one surface and not the others is a control with a documented bypass.

Residuals, stated plainly. A task-level override stricter than its agent-level gate loses that extra strictness if the operator rewrites the document without it — a narrowing of one task's protection, never an ungated agent, still human-approved. And like self-guard.ts this is Manager-side: it governs the Manager's three approval surfaces, not the Slack buttons or the MCP resume_conversation tool. EDDI#668's strict toolApprovals merge is what would let this guard relax later.

3. Docs access, and the architecture in the prompt

GET /administration/docs{,/{name}} are added to READ_ENDPOINTS. The backend built these in EDDI 6.2.0 specifically so an OpenAPI-generated agent picks them up as ordinary tools — the eddi://docs/* MCP resources covering the same files do not reach an agent, because EDDI's MCP client consumes tools and never calls resources/read. The allow-list simply never included them.

⚠️ This sets a hard EDDI 6.2.0+ floor for operator activation (findMissingEndpoints refuses when the spec lacks an allow-listed path). Accepted deliberately over an optional-endpoints tier, which would let the prompt promise a docs capability the agent was silently never granted. Documented in AGENTS.md and HANDOFF.md.

The system prompt also gains the platform's versioning model — agent → workflow → config by id and version, nothing edits in place — in both scopes, since "my change did nothing" is a read-only diagnosis question whose answer is the version chain.

4. Launcher moved from a floating FAB into both headers

The bottom-right position was collision avoidance, not design: that corner already holds sonner's toast viewport (z-999999999, which covered the launcher whenever a toast was up), ChatDrawer's composer, WorkforceBottomTabs, and workforce-dashboard's own MobileFab — the last of which won the hit test, so tapping the operator navigated to /workforce/new. Each collision bought a hardcoded offset (bottom-24, bottom-40) that held until the next thing landed there. It also had to be mounted four times, because AppLayout and WorkforceLayout's three viewport branches share no chrome slot.

The header is that slot. The drawer is now an inline icon button plus a panel anchored to it, so no offset is hardcoded at all; tab order improves as a side effect. Verified live at desktop and mobile widths.

5. claude-sonnet-5 everywhere

The anthropic provider default fed the agent wizard, group wizard, operator activation and Workforce team builder. Updated there plus every hardcoded copy: four group-wizard fallbacks, the team-builder placeholder, task memory, the dream loop, the cascade step card, and cascadeModelName across all 11 locales. Bedrock's stale anthropic.claude-sonnet-4-6-v1:0 becomes anthropic.claude-sonnet-5.

Stale copy fixed alongside: the dashboard discovery card and the operator empty state both still described a read-only operator.

Tests

4923 passing (328 files), type check and lint clean. New: gate-guard.test.ts (16). Rewritten to pin the new contracts rather than the old ones: the write-scope selection suite, the provisionOperator scope tests, and the operator-page scope chip.

ginccc added 3 commits August 11, 2026 16:56
…hells' headers

The bottom-right position was collision avoidance, not design. That corner
already holds sonner's toast viewport (z-999999999, which covered the
launcher outright whenever a toast was up), ChatDrawer's composer,
WorkforceBottomTabs, and workforce-dashboard's own MobileFab — the last of
which won the hit test, so tapping the operator navigated to
/workforce/new. Every collision bought a hardcoded offset (bottom-24, then
bottom-40) that held only until the next thing landed in that corner.

It also had to be mounted four times, because AppLayout and
WorkforceLayout's three viewport branches share no chrome slot. The header
is that slot: TopBar and WorkforceTopbar between them cover all four.

The drawer is now an inline icon button plus a panel anchored to it
(absolute end-0 top-full), so no offset is hardcoded at all. Tab order
improves as a side effect — the panel now follows its launcher in the DOM
instead of preceding it. Pending-approval dot, role gating, Escape-to-close,
focus restore and the shared conversation store are unchanged.

The test that pinned bottom-24/bottom-40 is replaced by one asserting the
panel positions relative to its launcher and reintroduces no viewport-fixed
offset.
…guard, and docs access

Three changes that together make the Platform Operator actually able to
operate the platform, plus the model-default sweep they surfaced.

WRITE BY DEFAULT. read_write is now the default scope and freely selectable
on first activation; read-only is the explicit opt-down. The old two-step
bootstrap (activate read-only, come back, reconfigure once a gate was
verified) gated the OFFER on facts activation now proves about the agent it
actually creates — verifyGateInstalled reads the gate back from the new
document, and the write canary refuses to leave a write-capable operator
deployed unless a real write provably paused. That is strictly stronger
evidence than a verification remembered from a predecessor agent, so
isWriteScopeAvailable now only checks that WRITE_ENDPOINTS is non-empty.
Caller-identity is no longer demanded either: on an OIDC deployment
authMode 'none' cannot activate at all, and on a no-auth deployment there
are no identities to attribute approvals to — requiring it made write scope
permanently unreachable exactly where EDDI gets evaluated.

LLM CONFIGS, SAFELY. llmstore writes are granted, so the operator can edit
an agent's system prompt and model — previously impossible and the single
biggest capability gap. That document can carry a gate
(Task.toolApprovals fully replaces the agent-level one), so the grant is
valid ONLY alongside the new gate-guard.ts, which hard-refuses (disables
Approve, like self-guard.ts) any llmstore write carrying that field, and
any whose body cannot be read in full to prove it does not. Absence is safe
rather than merely unchecked: a body without toolApprovals leaves the field
null, so the agent-level gate applies — the operator can change what an
agent says and runs, never what gates it. blocked-calls.ts now resolves
both guards in one place, because the three approval surfaces were each
inlining self-guard plus a copy of its i18n string and a second guard would
have triplicated that again.

DOCS. GET /administration/docs{,/{name}} were built by the backend (EDDI
6.2.0) precisely so an OpenAPI-generated agent picks them up as ordinary
tools — the eddi://docs/* MCP resources do not reach an agent. The
allow-list simply never included them. Adding them sets a hard EDDI 6.2.0+
floor for activation, accepted deliberately over an optional-endpoints tier
that would let the prompt promise docs the agent never got. The system
prompt also gains the versioning model (agent -> workflow -> config by id
and version, nothing edits in place) in BOTH scopes, since 'my change did
nothing' is a read-only diagnosis question.

MODEL DEFAULTS. claude-sonnet-5 is now the default and placeholder
everywhere — the anthropic provider default fed the agent wizard, group
wizard, operator activation and Workforce team builder, and every
hardcoded copy is updated (four group-wizard fallbacks, cascade, task
memory, dream loop, and cascadeModelName across all 11 locales). Bedrock's
stale anthropic.claude-sonnet-4-6-v1:0 becomes anthropic.claude-sonnet-5.

Stale copy fixed with it: the dashboard discovery card and the operator
empty state both still described a read-only operator.
…handoff docs

Version drives the sidebar badge via __APP_VERSION__ (vite define from
package.json).

AGENTS.md and HANDOFF.md both still described the pre-change operator:
read-only by default, llmstore permanently excluded, and no backend floor.
All three are now stated as they actually are, including that the llmstore
grant is valid only alongside gate-guard.ts and that the two must not be
separated.
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@ginccc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 58 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f7596cd-b301-4bce-be0d-968446648502

📥 Commits

Reviewing files that changed from the base of the PR and between 4e5bbb8 and 1f97d25.

📒 Files selected for processing (46)
  • AGENTS.md
  • HANDOFF.md
  • package.json
  • src/components/editors/agent-config-sections.tsx
  • src/components/editors/llm/cascade/__tests__/cascade-interactions.test.tsx
  • src/components/editors/llm/cascade/cascade-step-card.tsx
  • src/components/editors/llm/task-memory-section.tsx
  • src/components/layout/app-layout.tsx
  • src/components/layout/top-bar.tsx
  • src/components/operator/__tests__/operator-activation.test.tsx
  • src/components/operator/__tests__/operator-drawer.test.tsx
  • src/components/operator/operator-activation.tsx
  • src/components/operator/operator-drawer.tsx
  • src/components/workforce/wizard/team-builder.tsx
  • src/components/workforce/workforce-layout.tsx
  • src/components/workforce/workforce-topbar.tsx
  • src/hooks/__tests__/use-operator-activation.test.tsx
  • src/hooks/use-operator.ts
  • src/i18n/locales/ar.json
  • src/i18n/locales/de.json
  • src/i18n/locales/en.json
  • src/i18n/locales/es.json
  • src/i18n/locales/fr.json
  • src/i18n/locales/hi.json
  • src/i18n/locales/ja.json
  • src/i18n/locales/ko.json
  • src/i18n/locales/pt.json
  • src/i18n/locales/th.json
  • src/i18n/locales/zh.json
  • src/lib/api/__tests__/operator.test.ts
  • src/lib/api/agent-setup.ts
  • src/lib/api/operator.ts
  • src/lib/model-suggestions.ts
  • src/lib/operator/__tests__/gate-guard.test.ts
  • src/lib/operator/__tests__/system-prompt.test.ts
  • src/lib/operator/__tests__/tool-scopes.test.ts
  • src/lib/operator/blocked-calls.ts
  • src/lib/operator/gate-guard.ts
  • src/lib/operator/system-prompt.ts
  • src/lib/operator/tool-scopes.ts
  • src/pages/__tests__/operator.test.tsx
  • src/pages/approvals.tsx
  • src/pages/conversation-detail.tsx
  • src/pages/dashboard.tsx
  • src/pages/group-wizard.tsx
  • src/pages/operator.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Makes the Platform Operator write-capable by default, expands its tools, and moves its launcher into shared headers.

Changes:

  • Adds gated LLM configuration writes and documentation access.
  • Defaults activation to read/write and centralizes approval blocking.
  • Standardizes Claude Sonnet 5 defaults and relocates the operator launcher.

Reviewed changes

Copilot reviewed 45 out of 45 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/pages/operator.tsx Uses centralized approval guards and updated copy.
src/pages/group-wizard.tsx Updates model fallbacks.
src/pages/dashboard.tsx Updates operator discovery copy.
src/pages/conversation-detail.tsx Uses centralized approval guards.
src/pages/approvals.tsx Uses centralized approval guards.
src/pages/__tests__/operator.test.tsx Updates scope and model expectations.
src/lib/operator/tool-scopes.ts Expands tools and defaults write availability.
src/lib/operator/system-prompt.ts Adds docs, architecture, and LLM-edit guidance.
src/lib/operator/gate-guard.ts Detects gate-carrying LLM writes.
src/lib/operator/blocked-calls.ts Combines hard approval refusals.
src/lib/operator/__tests__/tool-scopes.test.ts Updates allow-list tests.
src/lib/operator/__tests__/system-prompt.test.ts Tests new prompt guidance.
src/lib/operator/__tests__/gate-guard.test.ts Tests the new gate guard.
src/lib/model-suggestions.ts Adds current Claude model suggestions.
src/lib/api/operator.ts Changes operator defaults.
src/lib/api/agent-setup.ts Changes provider model defaults.
src/lib/api/__tests__/operator.test.ts Tests default write provisioning.
src/i18n/locales/zh.json Updates Chinese strings.
src/i18n/locales/th.json Updates Thai strings.
src/i18n/locales/pt.json Updates Portuguese strings.
src/i18n/locales/ko.json Updates Korean strings.
src/i18n/locales/ja.json Updates Japanese strings.
src/i18n/locales/hi.json Updates Hindi strings.
src/i18n/locales/fr.json Updates French strings.
src/i18n/locales/es.json Updates Spanish strings.
src/i18n/locales/en.json Updates English strings.
src/i18n/locales/de.json Updates German strings.
src/i18n/locales/ar.json Updates Arabic strings.
src/hooks/use-operator.ts Reframes gate verification as monitoring.
src/components/workforce/workforce-topbar.tsx Adds the operator launcher.
src/components/workforce/workforce-layout.tsx Removes floating launcher mounts.
src/components/workforce/wizard/team-builder.tsx Updates model placeholder.
src/components/operator/operator-drawer.tsx Converts the FAB into a header panel.
src/components/operator/operator-activation.tsx Defaults scope selection to read/write.
src/components/operator/__tests__/operator-drawer.test.tsx Tests header-relative positioning.
src/components/operator/__tests__/operator-activation.test.tsx Tests write-first activation.
src/components/layout/top-bar.tsx Adds the operator launcher.
src/components/layout/app-layout.tsx Removes the floating launcher.
src/components/editors/llm/task-memory-section.tsx Updates memory model defaults.
src/components/editors/llm/cascade/cascade-step-card.tsx Updates cascade placeholder.
src/components/editors/llm/cascade/__tests__/cascade-interactions.test.tsx Updates placeholder expectation.
src/components/editors/agent-config-sections.tsx Updates dream-loop model default.
package.json Bumps the application version.
HANDOFF.md Documents scope and backend-floor changes.
AGENTS.md Documents operator safety architecture.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/lib/operator/tool-scopes.ts
Comment thread src/lib/operator/gate-guard.ts
Comment thread src/lib/operator/tool-scopes.ts
Comment thread src/lib/operator/blocked-calls.ts Outdated
Comment thread HANDOFF.md
…ned preview

Addresses CodeRabbit's review on #137. Two of the five were real security
defects, and the first one invalidated this PR's core justification.

ENFORCE THE GATE READ-BACK. isWriteScopeAvailable stopped demanding a gate
verified on a PREVIOUS operator, on the stated grounds that activation
proves the gate about the agent it actually creates. But verifyGateInstalled
was only REPORTED — reportOperatorGateStatus, then proceed — so that
justification was false as written, and a read_write activation could leave
a deployed operator whose gate never verified. The write canary is not a
substitute: it provokes ONE endpoint (a descriptor PATCH), so it proves the
patch pattern pauses and says nothing about http.post/put/delete; a document
gating only some methods passes the canary with the rest ungated. Now
enforced for read_write (roll back, then throw with the reason
gateLooksInstalled gave) and deliberately still advisory for read_only,
where an unverified operator is useless rather than dangerous.

DO NOT TRUST AN UNPINNED PREVIEW. gate-guard read requestPreview.body
without checking requestPinned. Per PendingToolCallView's own contract an
unpinned http call is previewed best-effort and 'can still change before it
runs' — so a body inspected and found gate-free can execute carrying
toolApprovals. That is a bypass of the control, not a gap in it. Unpinned
llmstore writes are now refused, same fail-closed rule as a truncated body.

FALSE DIAGNOSIS. unverifiable-body covers truncation AND parse failure, but
the message named only size, so a small malformed payload was blocked with a
wrong explanation. Each refusal mode now has its own accurate string;
blockedGateUnpinned added across all 11 locales.

TWO STALE DOCS. tool-scopes' 'Deliberately NOT here' still listed every
llmstore write as excluded, contradicting the grant a few lines above in the
same load-bearing comment. HANDOFF still said no llmstore write and that
prompts/models cannot be edited. Both corrected, and both now state that the
grant and gate-guard must not be separated.

Tests: new use-operator-activation.test.tsx (4) covering the enforcement,
mutation-checked — disabling the enforcement fails the two discriminating
cases and leaves the two read_only guards passing, as intended. Its OpenAPI
fixture is generated FROM the allow-list so it cannot drift. Plus an
unpinned-request case in gate-guard.test.ts. 4928 green.
@ginccc

ginccc commented Aug 11, 2026

Copy link
Copy Markdown
Member Author

All five addressed in 1f97d254. Two were real security defects, and the first one invalidated this PR's core justification — thank you for catching it.

1. Gate read-back was never enforced (tool-scopes.ts:344) — you are right, and this was the important one

You identified the exact hole. isWriteScopeAvailable stopped demanding a gate verified on a previous operator on the stated grounds that "activation proves the gate about the agent it actually creates" — but verifyGateInstalled's result was only reported (reportOperatorGateStatus, then proceed). So that justification was false as written, and a read_write activation could leave a deployed operator whose gate never verified.

Your point about the canary is also exactly right and I had glossed it: it provokes one endpoint (PATCH /descriptorstore/descriptors/{id}), so it proves the patch pattern pauses and says nothing about http.post:* / http.put:* / http.delete:*. A document gating only some methods passes the canary with the rest ungated. The two checks are complementary — the read-back inspects the whole pattern set (gateLooksInstalled), the canary proves runtime enforcement — and I had been leaning on the canary to cover both.

Now enforced exactly as you proposed: read_write + unverified read-back → roll back (undeploy, delete, clear the config variable) and throw, carrying gateLooksInstalled's own reason so the admin can act. Deliberately still advisory for read_only, where an unverified operator is useless rather than dangerous.

New use-operator-activation.test.tsx covers it, and I mutation-checked it: disabling the enforcement fails the two discriminating cases and leaves the two read_only guards passing, which is the intended split. Its OpenAPI fixture is generated from the allow-list so it can't drift out of sync.

2. Unpinned preview (gate-guard.ts:137) — also right, and it was a bypass rather than a gap

I read requestPreview.body without checking requestPinned. Per PendingToolCallView's own contract an unpinned http call is previewed best-effort and "can still change before it runs" — so a body I inspect and find gate-free can execute carrying toolApprovals. Inspecting it proves nothing. Unpinned llmstore writes are now refused under a distinct unpinned-request reason, same fail-closed rule as a truncated body, with a test.

3. False diagnosis (blocked-calls.ts:72) — agreed

unverifiable-body covers truncation and parse failure while the message named only size, so a small malformed payload was blocked with a wrong explanation. An approver acting on a wrong reason is worse served than one given a vaguer true one. Each refusal mode now has its own accurate string; blockedGateUnpinned added across all 11 locales.

4 & 5. Two stale docs — agreed, and both were self-contradictions

tool-scopes.ts's "Deliberately NOT here" still listed every llmstore write as excluded, contradicting the grant a few lines above in the same load-bearing comment. HANDOFF.md still said no llmstore write and that prompts/models cannot be edited. Both corrected, and both now state explicitly that the grant and gate-guard.ts must not be separated.


4928 tests green, type check and lint clean.

@ginccc
ginccc merged commit 2b60f8f into main Aug 11, 2026
4 checks passed
@ginccc
ginccc deleted the feat/operator-write-by-default branch August 11, 2026 18:07
ginccc added a commit that referenced this pull request Aug 11, 2026
One textual conflict: src/pages/group-wizard.tsx, adjacent import lines —
main added useAvailableStyles/isStyleSupported, this branch added
RefetchErrorNotice. Kept both.

Checked what the merge changed underneath, since none of it conflicts textually:

- AppLayout still applies p-6 inside @container/main mx-auto max-w-screen-2xl,
  so the double-padding fix still holds, and no page has reacquired its own p-6.
- Sidebar and TopBar props are unchanged, so the design-sync previews still
  typecheck against them.
- Every count the docs assert is unchanged: 11 ui / 13 shared / 7 layout, 40
  pages, 29 space-y-6, 19 ErrorState, 9 EmptyState, 6/6 ViewToggle persisted,
  h1 sizes 22 text-3xl to 18 text-2xl.
- All five --color-sidebar* tokens still reach :root.
- All 11 locales are valid JSON and still carry gdpr.statusUnknown.
- HANDOFF.md auto-merged without losing any of main's operator edits
  (WRITE_ENDPOINTS 22 -> 24, gate-guard, the EDDI 6.2.0+ floor).

One regression the merge introduces, recorded in .design-sync/NOTES.md rather
than silently absorbed: PR #137 moved OperatorDrawer out of AppLayout and into
top-bar.tsx as a static import. TopBar is a synced component, so the
design-system bundle grew 4.00 -> 4.17 MB (+179 KB) and now carries the operator
tool-scope allow-list. Monaco is still excluded. NOTES.md documents the two ways
out (lazy-load the drawer, or drop TopBar from the surface) and the esbuild
command to measure either.

Gates on the merged tree: 4977 tests passing (331 files), build green,
tsc -b green.
ginccc added a commit that referenced this pull request Aug 11, 2026
TopBar is a synced component and, since PR #137 moved the operator launcher into
both shells' headers, it statically imports OperatorDrawer — dragging the
tool-scope allow-list (WRITE_ENDPOINTS), the activation flow and the operator
chat into _ds_bundle.js. That is the exact creep the scoped entry exists to
prevent.

cfg.tsconfig now points at a new tsconfig.ds-bundle.json whose paths map
@/components/operator/operator-drawer to .design-sync/stubs/operator-drawer.tsx.
The stub renders the launcher button in its resting state and nothing else — the
panel is behaviour, not design surface, needing an operator config, a chat
transcript and an approval stream that no preview has.

Measured through the converter's own resolver, not a stand-in: tool-scopes,
WRITE_ENDPOINTS, operator-activation and useOperatorChat all go to 0, and the
bundle drops 4.36 -> 4.26 MB (-99 KB). monaco/vscode stay 0.

tsconfig.design-sync.json now EXTENDS the bundle tsconfig instead of repeating
its paths, so the type-check and the bundle can never disagree about where an
import resolves — which means tsc -b validates the stub against TopBar's real
usage. Verified by giving the stub a required prop: top-bar.tsx:208 fails with
TS2741.

Lazy-loading was tested first and does not work. The converter emits
format: 'iife', which esbuild cannot code-split, so React.lazy(() => import(...))
is inlined into the same file — a probe entry doing nothing but that still
carried tool-scopes and WRITE_ENDPOINTS. OperatorDrawer also renders the launcher
itself, so it is mounted on every page and would load immediately anyway.

Landmine found and pinned along the way: tsconfigPathsPlugin strips comments with
a regex and JSON.parses the result, returning null on any throw — which silently
drops alias resolution for the WHOLE bundle rather than failing. The "@/*" key
contains a block-comment opener, so one stray closer anywhere after it deletes
the entire paths object. tsconfig.design-sync.json already tripped this via its
include globs, which is why the converter reads a dedicated glob-free file.
design-sync-tsconfig.test.ts pins the parse, the wildcard, the stub mapping, its
ordering ahead of the wildcard, and the absence of a closing delimiter.

Tests 5043 -> 5047.
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.

2 participants