Shell: single-product (primary-product) mode so a sole-product app owns the root surface - #65
Merged
Conversation
Add a configurable primary-product shell mode to apps/desktop so a sole-product app (e.g. OrinSync) owns the root surface without forking the shell. - shell/config.ts: ShellConfig.primaryProduct — the one-line config point a consumer sets (unset = multi-product default). - shell/productRegistry.tsx: additive DesktopProduct registry (the frontend mirror of the product-module seam; a product adds one entry, no foundation edit). - shell/resolveShellLayout.ts: pure, product-agnostic decision fn returning the layout descriptor; throws ShellConfigError on an unregistered namespace (fail-fast, never a silent fallback). - shell/Shell.tsx: thin React consumer — single-product renders only the product root (no nav/chrome); multi-product renders header + Products nav + platform panels + active product. - shell/resolveShellLayout.test.ts: covers both modes + the error path (node, no DOM). - App.tsx: resolves the layout once and renders <Shell>. The product-module seam contract is unchanged: no edits under crates/, services/, migrations/, or product_module.rs. Pure config + presentation.
…gle-product chrome (#63) Address cross-vendor review (Forge) on PR #65: - Shell now consumes ShellLayout.showPlatformChrome to gate the platform header + account/billing panels (was computed but never read — a dead layout flag). - Replace the redundant unknown-namespace test with two higher-value cases: an empty-string primaryProduct must throw (the === undefined guard does not treat "" as unset), and the error names the registered namespaces. - Document that single-product mode deliberately hides platform/billing chrome and does not invent billing UI; ShellLayout.showPlatformChrome is the seam for a follow-up platform-chrome slot.
Owner
Author
Cross-vendor adversarial review (Forge / GPT-5.5, read-only) — resolvedVerdict was CONCERNS (no critical bugs, core decision logic correct, default multi-product path regression-free, no seam violations). Three findings, all addressed in 683336e:
Follow-up (not filed as an issue — flagged for the maintainer)Platform-chrome slot for single-product mode (billing/account reachability): decide how a single-product app surfaces account/subscription affordances without forking the foundation (a kit-provided slot gated by Gates after fixes: |
AojdevStudio
added a commit
that referenced
this pull request
Jun 15, 2026
…#67) Single-product mode (#63 / PR #65) hid all platform chrome, leaving a single-product SaaS app no kit-provided way to reach account/billing. `ShellLayout.showPlatformChrome` was the seam for it but was a dead flag (the branch returned `<Root />` before it was read). Make it live, opt-in (ADR-0004, issue #66 option 1 + account/billing-only): - `ShellConfig.showPlatformChrome` (single-product only) drives the slot; `resolveShellLayout` reads `config.showPlatformChrome ?? false`, so the product still owns the root by default. - `PlatformChromeSlot` composes the existing authority-backed `MePanel` + `BillingPanel` alongside the product root. It places existing panels; it invents no billing UI. `AdvancedReportPanel` (a product feature) is excluded. - Multi-product mode unchanged; product-module seam untouched. Resolver behavior is unit-tested (the kit's tested surface — vitest in node, no DOM); `<Shell>` stays a thin consumer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #63
What & why
apps/desktopwas a flat walking-skeleton (App.tsx) that hardcoded the platformpanels + the Notes screen. The product-module seam presumes a multi-product shell
where each product registers under a
/<namespace>segment — but a sole-productapp (OrinSync, the first real product) is the app and should own the root surface,
not sit inside generic multi-product chrome.
This adds a first-class primary-product shell mode as pure config + presentation.
No router is introduced (the seam's
/<namespace>routing is still aspirational; thisissue is a boot-time layout decision, not navigation). The product-module seam contract
is untouched.
How
shell/config.ts—ShellConfig.primaryProduct, the one-line config point aconsumer sets (
shell.primaryProduct = "<namespace>"). Unset = multi-product default.shell/productRegistry.tsx— additiveDesktopProductregistry (frontend mirrorof the seam; a product adds one entry, no foundation edit). Notes registered with
its deny-by-default snapshot.
shell/resolveShellLayout.ts— pure, product-agnostic decision function returning{mode, products, primaryProduct, showProductNav, showPlatformChrome}; throwsShellConfigErroron an unregistered namespace (fail-fast, never a silent fallback).shell/Shell.tsx— thin React consumer: single-product renders only the productroot (no nav/chrome); multi-product renders header + Products nav + platform panels +
active product.
App.tsx— resolves the layout once and renders<Shell>.The testable logic lives in the pure resolver because vitest runs in node with no DOM;
the component is a humble consumer.
Acceptance criteria
shell/config.ts→primaryProduct).mode==="single-product",showProductNav===false,showPlatformChrome===false;Shellreturns just<Root />).Shell).crates/,services/,migrations/,product_module.rs).resolveShellLayout.test.ts: multi, single, product-agnostic, and the error path).Functional evidence
Runtime resolver demo (real execution, both modes + error):
Gates:
bun run test→ 6 files, 48 tests pass (43 prior + 5 new)bun run typecheck/bun run lint/bun run build→ clean (35 modules, 202.69 kB)cargo xtask gate --scope all→ PASSED (fmt, clippy, workspace tests, crate-edge, frontend lint/type/build, cargo-deny, leak scan, feature-key coverage)Self-review
Reviewed the full diff against the issue, the seam doc, and the project code-quality rules.
App.tsx58→19 lines; all platform surfaces relocated intact; small single-purpose modules, typed, noconsole, comments explain why. Stays strictly inside the slice — no router, no backend, no seam edits. No actionable findings. Cross-vendor adversarial review (Forge) folded in before merge.