docs: add exact clients tablo parity design
This commit is contained in:
parent
b1c1c595ab
commit
423d419893
1 changed files with 249 additions and 0 deletions
|
|
@ -0,0 +1,249 @@
|
|||
# Clients Exact Tablo Parity
|
||||
|
||||
**Date**: 2026-04-16
|
||||
**Status**: Draft
|
||||
**Supersedes**: `docs/superpowers/specs/2026-04-15-client-portal-tablo-parity-design.md`
|
||||
|
||||
## Overview
|
||||
|
||||
`clients.xtablo.com` is intended to be a portal for the clients of our clients. For the single-tablo experience, it must render the exact same UI surface as `app.xtablo.com` on `apps/main/src/pages/tablo-details.tsx`.
|
||||
|
||||
The current codebase does not guarantee that outcome because `apps/main` and `apps/clients` still compose the page separately. Shared sections and some shared CSS exist, but the full single-tablo route is not owned by one shared render surface.
|
||||
|
||||
The target is stricter than "close parity":
|
||||
|
||||
- same page shell
|
||||
- same header structure
|
||||
- same metadata row
|
||||
- same tab bar
|
||||
- same overview layout
|
||||
- same section framing
|
||||
- same responsive behavior
|
||||
- same route-level CSS source
|
||||
- same component tree for the single-tablo view
|
||||
|
||||
The only intended differences are:
|
||||
|
||||
- `apps/main` keeps URL-backed section state
|
||||
- `apps/clients` keeps in-memory section state
|
||||
- `apps/main` exposes full admin and mutation capabilities
|
||||
- `apps/clients` exposes a restricted client-safe capability set
|
||||
|
||||
## Problem Statement
|
||||
|
||||
`clients.xtablo.com` is not at feature parity today for structural reasons, not because of one isolated CSS bug.
|
||||
|
||||
### Current causes of drift
|
||||
|
||||
1. `apps/clients/src/pages/ClientTabloPage.tsx` reconstructs a client-specific page instead of rendering the same single-tablo surface as `apps/main`.
|
||||
2. `apps/clients/src/components/ClientLayout.tsx` owns a separate app shell, so spacing, header behavior, and responsive layout can drift from the main app.
|
||||
3. Shared CSS exists only partially. Route-level tokens and chat/page styling have been extracted in places, but the full single-tablo view is still not governed by one shared route stylesheet plus one shared render tree.
|
||||
4. Permissions are mixed into page composition instead of being expressed as a clean capability model. That forces `clients` to fork render logic instead of rendering the same surface with different behavior gates.
|
||||
|
||||
The result is predictable: any visual or structural change to the main tablo route risks being manually reimplemented in `clients`, and parity becomes a maintenance task instead of an invariant.
|
||||
|
||||
## Goals
|
||||
|
||||
- Make the single-tablo UI in `clients.xtablo.com` visually identical to `apps/main/src/pages/tablo-details.tsx`
|
||||
- Enforce parity through one shared single-tablo render surface
|
||||
- Share the route-level CSS and responsive behavior between both apps
|
||||
- Keep `apps/clients` on in-memory tab state
|
||||
- Keep `discussion` writable in client mode
|
||||
- Keep the rest of the client experience read-only or admin-hidden as approved
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Rebuilding the client portal as a separate visual concept
|
||||
- Introducing query-param section routing into `apps/clients`
|
||||
- Enabling client-side admin actions
|
||||
- Refactoring unrelated routes outside the single-tablo experience
|
||||
- Deleting the newer client-specific invite system from the codebase
|
||||
|
||||
## Hard Requirement
|
||||
|
||||
The UI must be the exact same.
|
||||
|
||||
That rules out maintaining two parallel page compositions for the single-tablo route. "Shared components plus duplicated page assembly" is not sufficient because parity will drift again. The single-tablo view must be rendered from one shared composition surface consumed by both apps.
|
||||
|
||||
## Chosen Approach
|
||||
|
||||
Create a shared single-tablo route surface in `packages/tablo-views` and make both apps consume it.
|
||||
|
||||
This shared surface owns the exact structure, responsive layout, CSS import, tab order, overview composition, and section framing for the route.
|
||||
|
||||
Each app becomes a thin adapter:
|
||||
|
||||
- `apps/main` passes full-capability handlers and URL-backed section state
|
||||
- `apps/clients` passes restricted capabilities and in-memory section state
|
||||
|
||||
This is a consolidation, not a styling pass.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Shared package ownership
|
||||
|
||||
`packages/tablo-views` should own the single-tablo route surface for both apps, including:
|
||||
|
||||
- header layout
|
||||
- title and icon/image block
|
||||
- metadata row
|
||||
- sticky tab navigation
|
||||
- overview block composition
|
||||
- section container layout
|
||||
- discussion full-height layout behavior
|
||||
- route-specific CSS for this surface
|
||||
|
||||
This shared surface should render the same DOM structure and use the same styling hooks regardless of app.
|
||||
|
||||
### App adapter ownership
|
||||
|
||||
`apps/main` should own:
|
||||
|
||||
- `?section=` query-param state
|
||||
- full mutation handlers
|
||||
- admin-only actions
|
||||
- share and invite workflows
|
||||
- main-only routing integrations
|
||||
|
||||
`apps/clients` should own:
|
||||
|
||||
- local in-memory tab state
|
||||
- client-safe data loading
|
||||
- capability restrictions
|
||||
- client-safe app shell concerns outside the single-tablo surface
|
||||
|
||||
### Key principle
|
||||
|
||||
The apps must differ by inputs, not by page composition.
|
||||
|
||||
## Capability Model
|
||||
|
||||
The shared single-tablo surface should branch on capabilities rather than on app identity.
|
||||
|
||||
Suggested capability contract:
|
||||
|
||||
- `canCreateTasks`
|
||||
- `canEditTasks`
|
||||
- `canEditEvents`
|
||||
- `canManageFiles`
|
||||
- `canManageMembers`
|
||||
- `canInviteMembers`
|
||||
- `canEditLayout`
|
||||
- `canWriteDiscussion`
|
||||
|
||||
### Approved client boundary
|
||||
|
||||
For `clients.xtablo.com`:
|
||||
|
||||
- `discussion`: writable
|
||||
- file read/download behavior: allowed where already supported
|
||||
- task edits: disabled
|
||||
- event edits: disabled
|
||||
- layout edits: hidden
|
||||
- member management: hidden or read-only
|
||||
- invite/share management: hidden
|
||||
|
||||
This preserves the same page structure while changing behavior safely.
|
||||
|
||||
## CSS And Responsiveness
|
||||
|
||||
Exact UI parity requires a single route-level CSS source for the single-tablo experience.
|
||||
|
||||
### Requirements
|
||||
|
||||
- the shared single-tablo surface imports one shared route stylesheet from `packages/tablo-views`
|
||||
- route-level tokens for navbar, metadata, sticky tabs, and discussion/chat visuals come from that shared stylesheet
|
||||
- responsive breakpoints and overflow behavior are not redefined independently in `apps/main` and `apps/clients`
|
||||
|
||||
### Constraint
|
||||
|
||||
`apps/clients` must not carry a competing route-specific single-tablo style layer that can override the shared surface in divergent ways.
|
||||
|
||||
App-local CSS can remain for app-wide concerns, but the single-tablo route must have one styling owner.
|
||||
|
||||
## State Model
|
||||
|
||||
The visual surface is shared, but tab state differs by app.
|
||||
|
||||
### Main app
|
||||
|
||||
- active section comes from `?section=...`
|
||||
- existing route behavior remains intact
|
||||
|
||||
### Clients app
|
||||
|
||||
- active section is stored in local React state
|
||||
- no query-param synchronization is required
|
||||
|
||||
This is acceptable because the user explicitly approved in-memory state for clients.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Phase 1: Consolidate the surface
|
||||
|
||||
- identify all remaining structure in `apps/main/src/pages/tablo-details.tsx` that is still page-owned instead of shared
|
||||
- move that structure into a shared single-tablo route surface in `packages/tablo-views`
|
||||
- make `apps/main` consume the shared surface first, preserving current behavior
|
||||
|
||||
### Phase 2: Convert the clients app into an adapter
|
||||
|
||||
- remove duplicated page composition from `apps/clients/src/pages/ClientTabloPage.tsx`
|
||||
- replace it with a thin adapter that passes client-safe data and capability flags into the shared surface
|
||||
- keep local in-memory tab state in the adapter only
|
||||
|
||||
### Phase 3: Normalize the shell
|
||||
|
||||
- ensure the surrounding shell and route-level CSS used by the single-tablo surface are shared consistently
|
||||
- keep `ClientLayout` responsible only for client-portal app concerns, not for redefining the single-tablo view
|
||||
|
||||
### Phase 4: Lock parity with tests
|
||||
|
||||
- add tests that assert `main` and `clients` render the same shared single-tablo structure
|
||||
- add client-mode tests for capability restrictions
|
||||
- add responsive regression coverage where practical
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Verification must prove parity, not just correctness.
|
||||
|
||||
### Automated
|
||||
|
||||
- shared render tests for the single-tablo route surface in `packages/tablo-views`
|
||||
- adapter tests proving `apps/main` and `apps/clients` pass different state/capabilities into the same surface
|
||||
- regression tests that the client app hides or disables admin-only actions while keeping discussion writable
|
||||
- targeted CSS contract tests ensuring both apps import the same shared route stylesheet
|
||||
|
||||
### Manual
|
||||
|
||||
Side-by-side comparison between:
|
||||
|
||||
- `apps/main` `/tablos/:tabloId`
|
||||
- `apps/clients` `/tablo/:tabloId`
|
||||
|
||||
At minimum verify:
|
||||
|
||||
- desktop layout
|
||||
- mobile layout
|
||||
- sticky tabs
|
||||
- overview cards
|
||||
- discussion layout
|
||||
- header wrapping and spacing
|
||||
- empty states
|
||||
|
||||
## Risks
|
||||
|
||||
- `apps/main/src/pages/tablo-details.tsx` may still contain too much mixed business logic and render logic, making extraction noisy
|
||||
- some shared sections may still assume main-app permissions implicitly
|
||||
- partial CSS ownership may continue to cause drift if not fully normalized
|
||||
- client-safe data access may reveal places where the UI currently assumes admin or member-level data is always present
|
||||
|
||||
## Success Criteria
|
||||
|
||||
This work is successful when:
|
||||
|
||||
- `clients.xtablo.com` renders the same single-tablo UI as `app.xtablo.com`
|
||||
- future UI changes to the single-tablo route normally require edits in one shared place
|
||||
- `apps/clients` remains in-memory for tab state
|
||||
- client restrictions match the approved boundary
|
||||
- discussion remains writable in client mode
|
||||
- parity is enforced structurally, not maintained manually
|
||||
Loading…
Reference in a new issue