Skip to content

feat(editor): host-registered sidebar modules, rail control, panel layout (0.3.8) - #21

Open
lightninglu10 wants to merge 5 commits into
codepress-mainfrom
feat/host-sidebar-modules
Open

feat(editor): host-registered sidebar modules, rail control, panel layout (0.3.8)#21
lightninglu10 wants to merge 5 commits into
codepress-mainfrom
feat/host-sidebar-modules

Conversation

@lightninglu10

@lightninglu10 lightninglu10 commented Aug 25, 2026

Copy link
Copy Markdown

What

Lets a host own the editor's left sidebar: register its own modules into the rail, decide the rail's order, suppress built-ins it does not want, and give module panels the layout context they need.

Modules. EditorHost.sidebarModules entries ({ id, label, icon, Panel }) join the MediaSidebar rail as host:<id> tabs, with host-owned labels and host-rendered panel content. Icons and panels cross the package boundary as React components — react/react-dom are peer dependencies, so host and surface share one React copy.

Rail control. EditorHost.sidebarRail declares the exact tabs to show, in the exact order, as built-in ids and host:<id> module ids. Anything omitted is hidden, so one field both reorders the rail and suppresses built-ins. Capability gating still runs first — a rail can only reorder and subtract from what the capabilities already allow, never add to it. Ids matching no available tab are dropped, repeats collapse to the first mention, and a rail matching nothing at all falls back to the default rather than leaving the editor with no navigation.

Imperative handle. FreeCutEditorSurface accepts an optional apiRef receiving a FreeCutEditorSurfaceApi: openSidebarModule(id) selects a module's tab and opens the panel, closeSidebar() closes it. This lets the host surface a module exactly when host-side work needs attention — e.g. auto-opening a transcription control when an agent proposal is waiting on approval.

Behavior

  • Module panels mount on first activation and stay mounted across tab switches and authoritative snapshot installs, so in-flight host work (job polling, proposal lists) survives the user browsing other tabs.
  • Panels receive { active, collapsed, width } rather than { active }, so a panel can pause work while it is not selected and adapt to a sidebar resize without measuring the DOM. Widening the props is source-compatible with an existing ({ active }) => … panel.
  • EditorSidebarTab in editor-workspaces.ts gains a host:${string} member so persisted-layout coercion recognizes host tabs.
  • Hosts without sidebarModules / sidebarRail are unchanged.

One resolver, two readers

runtime.ts previously carried its own copy of the capability rules (tabVisibleInHostMode) to decide whether a persisted activeTab survives a snapshot install. With sidebarRail in play that copy would drift — a suppressed tab could survive an install and sit there with no rail button to leave it.

Both readers now share one resolver, hostRailTabIds (src/features/editor/host/sidebar-rail.ts): the sidebar renders from it, the runtime tests against it. Two consequences worth reviewing:

  • The post-install fallback is the rail's first entry, not a hardcoded 'media' — a host may drop media from its rail entirely.
  • openSidebarModule fails closed for any tab the rail does not show, not just for unregistered ids. Opening a tab with no rail button would strand the user, and the next snapshot install would reset it anyway.

Validation

  • 73/73 host tests pass, including 11 media-sidebar-modules.test.tsx cases against the real MediaSidebar: rail registration, panel rendering, { collapsed, width } propagation across resize and collapse, snapshot-install survival, unregistered-tab reset, apiRef open/close, plus six sidebarRail cases (declared order, built-in suppression, capability denial, unknown-id and repeat dropping, empty-match fallback, rail-head fallback on snapshot install, and fail-closed openSidebarModule for a suppressed module).
  • npm run check, check:boundaries, check:deps-contracts, check:edge-budgets, check:unused-exports, and the fallow changed-health gate all pass. MediaSidebar keeps its pre-existing tab-orchestrator complexity suppression; the module logic stays extracted into HostModulePanels / visibleRailCategories, and the rail resolution moved out entirely into sidebar-rail.ts.
  • npm run package:editor-surface + npm run test:editor-surface:consumer pass against the packed 0.3.8 tarball, confirming the hand-written index.d.ts ships sidebarRail, EditorSidebarTab, and EditorSidebarModulePanelProps.

Release

Bumps the package to 0.3.8. Both 0.3.6 and 0.3.7 are already published to npm from codepress-main release commits without any of this work, and npm versions are immutable — the publish workflow skips a version that already exists, so this cannot reuse either. codepress-main is merged into the branch (twice, as it kept releasing) to stay current.

Merging to codepress-main publishes 0.3.8 via the trusted-publishing workflow. First consumer: CodePress repins to 0.3.8 and moves its route-owned Transcribe panel into this rail (separate PR in quantfive/codepress, which cannot install until 0.3.8 is on npm).

EditorHost gains sidebarModules: entries ({ id, label, icon, Panel }) join
the MediaSidebar rail as host:<id> tabs after the built-in categories, with
host-owned labels and panels rendered in the sidebar panel area. Module
panels mount on first activation and stay mounted across tab switches and
authoritative snapshot installs so in-flight host work (job polling,
proposal lists) survives.

FreeCutEditorSurface also accepts an apiRef that receives a
FreeCutEditorSurfaceApi — openSidebarModule(id) selects a registered
module's tab and opens the panel (unregistered ids fail closed), and
closeSidebar() closes the panel — so the host can surface a module exactly
when host-side work needs attention.

Publishes as @quantfive/freecut-editor-surface 0.3.6.
@codepress-reviewer-dev

Copy link
Copy Markdown

Starting initial review of 8ed1231a from baseline ad5b52ca... 👀

View automation run

@codepress-reviewer-dev codepress-reviewer-dev Bot 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.

The ownership boundary is sound: hosts provide registered module metadata and panel components, while the editor owns the rail/tab lifecycle. The host:<id> identity is applied consistently across rendering, persisted-layout coercion, authoritative snapshot reconciliation, and the imperative surface API.

I found no actionable issues in this change. The feature is cohesive and ready to merge.

Reviewed `ad5b52ca`...`8ed1231a`. Diff collection and GitHub posting were handled by the automation.

View automation run

@codepress-reviewer-dev

Copy link
Copy Markdown

Review head 8ed1231a4ba16ef42e42b3217034863938a41de1; round 1; 12 reviewed file(s), 0 pending/skipped file(s). 0 subsystem record(s) retained; 0 omitted.

View automation run

…layout

EditorHost gains sidebarRail: an explicit ordered list of rail tabs, as
built-in ids and `host:<id>` module ids. Anything omitted is hidden, so one
field both reorders the rail and suppresses built-ins a host does not want.
Capability gating still runs first — a rail can only subtract from and reorder
what the capabilities already allow, never add to it — and ids matching no
available tab are dropped, as are repeats after the first. A rail that matches
nothing falls back to the default rather than leaving the editor with no
navigation.

The resolution lives in one place (hostRailTabIds) that both the sidebar and
the runtime read, replacing runtime.ts's parallel tabVisibleInHostMode copy of
the same capability rules. That is what keeps a suppressed tab from surviving
an authoritative snapshot install, and it makes the post-install fallback the
rail's first entry instead of a hardcoded 'media' — a host is free to drop
media from its rail. openSidebarModule now fails closed for any tab the rail
does not show, not just for unregistered ids.

Module panels receive { active, collapsed, width } instead of { active }, so a
panel can pause work while it is not selected and adapt to a sidebar resize
without measuring the DOM. Widening the props is source-compatible with an
existing ({ active }) => … panel.

Publishes as 0.3.7: 0.3.6 is already on npm from the release-0-3-6 branch
without the sidebar-module work, and npm versions are immutable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lightninglu10 lightninglu10 changed the title feat(editor): host-registered sidebar modules in the editor rail (0.3.6) feat(editor): host-registered sidebar modules, rail control, panel layout (0.3.7) Aug 26, 2026
…idebar-modules

# Conflicts:
#	packages/freecut-editor/package.json
@lightninglu10

Copy link
Copy Markdown
Author

Heads up for re-review: the approval above predates the last two commits.

Since then this PR gained sidebarRail (rail order + built-in suppression), widened module panel props to { active, collapsed, width }, and collapsed runtime.ts's duplicate tabVisibleInHostMode capability rules into the shared hostRailTabIds resolver that the sidebar and runtime now both read. Two behavior changes worth a look specifically:

  1. the post-snapshot-install fallback tab is now the rail's first entry rather than a hardcoded 'media', and
  2. openSidebarModule fails closed for a registered module the rail suppresses, not only for unregistered ids.

Version moved 0.3.6 -> 0.3.7 because 0.3.6 is already on npm from release-0-3-6 without this work. codepress-main is merged in to resolve the version-line conflict that created.

"Verify and package baseline" failed once on the first push and passed on re-run with no code change — it is the build-twice-and-cmp determinism job, which had already flaked once on codepress-main (run 32784386823). Flagging rather than claiming it is sound; I could not reproduce it locally because the provenance gate rejects a non-merge-ref HEAD.

lightninglu10 and others added 2 commits August 25, 2026 18:21
codepress-main published 0.3.7 from its own release commit while this branch
was open, and that build carries none of the sidebar-module work. npm versions
are immutable, so this cannot ship as 0.3.7 — the publish workflow would skip a
version that already exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lightninglu10 lightninglu10 changed the title feat(editor): host-registered sidebar modules, rail control, panel layout (0.3.7) feat(editor): host-registered sidebar modules, rail control, panel layout (0.3.8) Aug 26, 2026
@lightninglu10

Copy link
Copy Markdown
Author

Rebumped 0.3.7 -> 0.3.8. While this branch was open, codepress-main landed #22, #23 and a chore(release): 0.3.7 that published 0.3.7 to npm — and I verified that published tarball carries no sidebar API at all (no sidebarModules, no sidebarRail). npm versions are immutable and the publish workflow skips a version that already exists, so 0.3.7 was no longer available to this PR. codepress-main is merged in again; the branch is current with b9f0f9d4.

Note for whoever merges: this is the second time a release commit on codepress-main has taken the version this PR was holding (0.3.6, then 0.3.7). Worth considering whether feature PRs should carry a version bump at all, or whether the release commit should own it exclusively — otherwise every long-lived feature PR races the release branch for the same line in packages/freecut-editor/package.json.

CodePress is currently pinned to 0.3.7, which installs fine but has none of this. It repins to 0.3.8 after this merges and publishes.

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.

1 participant