Decision 0023 — Role×Screen Office/Mobile Portal Boundary¶
- Status:
ACCEPTED - Date:
2026-09-02 - Deciders:
CTC Product Owner,Architecture Review,NS3-046 - Affected:
PRODUCT.md,DESIGN.md,UI_FOUNDATION.md,ROADMAP.md,workspacePageCatalog.ts,AppShell.tsx,portal-surface-authority.test.mjs - Related:
0016-six-phase-delivery-and-tenant-productization.md,0017-tenant-runtime-authority.md,NS3-046
Context¶
Phase 3 must reconstruct every current CTC screen. The current AppShell.tsx is Office-oriented with workspace tabs. The same route registry contains field screens (driver-operation, yard-instruction, inbound-loader) that require one-task-at-a-time execution. Viewport-based routing would divert Office users on phones to the wrong portal, and a user-level mobile flag would create a tenant-specific source branch.
Current facts (frozen 2026-09-02, qa@93ffba688):
AppShell.tsx:60owns Office tabs;workspacePageCatalog.ts:12owns 96 registered slugs including 3 field candidatesworkspaceSession.ts:40resolves principal via persistedT_ROLE/T_MENU+@ctc/policymodule/capability; noUser.mobileauthRoutes.ts:30preserves sanitizednextpath; no Portal contractdriver-operation/yard-instruction/inbound-loaderarePARTIAL/TARGET_ROLEbut lack Portal classificationNS3-040checkpoint preserves Office planning (yard-dispatch);NS3-045owns visual evidence only
Problem: without an explicit Portal classification, the same narrow viewport or a future mobile user attribute could grant, deny or select Portal access, duplicating business authority and breaking Office-on-phone support.
Decision¶
Admit Option B — Separate Portal Shells Inside Current apps/web with a single exact-screen classification owner.
PortalKind = OFFICE | MOBILE | DEFERRED(deferred = not in current Phase-3 scope)- Classification owner:
new-system/apps/web/src/composition/portalClassification.ts(new, tenant-neutral) - input: exact
slug(e.g.,driver-operation),workspacePageCatalogentry,legacyOperationsCatalogfamily - output:
PortalKind+requiresCapability+provenance(Legacy PHP path or catalog) OFFICEdefault;MOBILEonly fordriver-operation,yard-instruction,inbound-loaderwith explicit ADR per additional field screen- Access:
portalAvailability = derived( authorizedScreens = resolve( Role/Menu × module × capability ) ) - server resolves effective
Role/MenuviaworkspaceSession+T_ROLE/T_MENUread; client never decides OFFICE/MOBILEavailability derived, not persisted; noUser.mobile,device.isMobile,viewport → authorization- fail-closed on unknown Role/Menu/module/capability
- Shells:
AppShell.tsxremains Office (tabs);MobilePortalShell.tsx(new undersrc/composition/) is tabless (home/list → detail → command/readback) - Routing:
app/(office)/[slug]/page.tsxandapp/(mobile)/[slug]/page.tsxshare the same classification owner; direct links preserve requested Portal if authorized, otherwise403/404ormodule-disabled - Generic login: if both Portals authorized, render explicit Portal chooser (approved by this ADR, not viewport); if one, redirect to that Portal's home; if none,
access-denied - Narrow viewport: Office Portal remains reachable with horizontal scroll for dense tables; no field omission
Consequences¶
- Positive: one classification owner serves both shells; no duplicate catalog; no device-based authority; Office-on-phone preserved; existing
Delivery/Yard/Transloadcommand/query/API/readback owners reused - Negative:
workspacePageCatalogmust be split to exact-screen entries;AppShellstate must be factored to avoid Office/Mobile tab leakage - Production files:
+2(portalClassification.ts,MobilePortalShell.tsx),~3modified (AppShell, catalog,authRoutesnext handling) — withinNS3-047limits12/12/1600 - Public exports:
0new;dependency edges +2(shell → classification) - Tenant-specific symbols:
0(classification is tenant-neutral, selection via Role/Menu) - Architecture exceptions:
0 - Rollback: revert classification + shells, restore single
AppShelltab bar; no DB/provider effect - Revisit: if a fourth field screen requires Mobile or if Office table density cannot be preserved at 390px without omission, reopen ADR
Alternatives¶
Alternative 1 — One AppShell With Responsive/Role Modes¶
Keep single shell, hide tabs for field screens via conditional CSS/role checks.
- Pros: fewest files, low removal cost
- Cons: one shell owns contradictory Office/field navigation; role/device conditions leak through
AppShellstate; fails separate Portal boundary requirement - Verdict: REJECTED — does not satisfy Product-owner separate Portal direction
Alternative 2 — Separate Portal Shells Inside Current apps/web (Selected)¶
As decided above.
- Pros: distinct navigation, single app/release/lineage, single classification owner, reuses business owners
- Cons: requires catalog split and shell factoring; moderate removal cost
- Verdict: ACCEPTED — measured ownership, bounded files, preserves Office-on-phone
Alternative 3 — Separate Mobile Web App / Deployment¶
New apps/mobile with separate hostname, auth, catalog, release.
- Pros: strongest isolation
- Cons: duplicate auth/catalog/dependencies/releases for same Product; cross-Portal consistency/rollback high cost; violates one Product/one lineage
- Verdict: REJECTED — evidence that Option B meets immediate operational need; separate deployment deferred to explicit Horizon 3 decision
Revisit¶
Reopen if:
- a new field screen is proposed for Mobile
- Office-narrow horizontal scroll is rejected by Product Owner
portalClassificationdrifts fromworkspacePageCatalogorT_ROLE/T_MENUauthority
References¶
NS3-046.md§1-10 (Product-owner direction, facts, authority model, contracts)ARCHITECTURE_CHANGE_METHOD.md(facts→problem→options→ADR→separate implementation→verification)EXECUTION.md(NS3-046 ACTIVE, NS3-045 ACTIVE parallel per NS3-049)
---¶
Source:
new-system/docs/decisions/0023-role-screen-portal-boundary.md— ctc repository · This page is an English translation of the source document. Identifiers, file paths and code symbols are unchanged.