Skip to content

docs: Plan 9 dashboard locked decisions - #24

Merged
messagesgoel-blip merged 2 commits into
mainfrom
docs/plan-9-dashboard
Aug 1, 2026
Merged

docs: Plan 9 dashboard locked decisions#24
messagesgoel-blip merged 2 commits into
mainfrom
docs/plan-9-dashboard

Conversation

@messagesgoel-blip

Copy link
Copy Markdown
Collaborator

Summary

  • Add Plan 9 doc for the product dashboard (design §11 / §13 step 13)
  • Lock: top-level Vite SPA, Whimsy-pattern scaffold (not a Whimsy clone), Clerk OIDC + CI API-key mode, missing CP APIs, fixed-tier Stripe, PR split A–D
  • Update HANDOVER to Plan 9 docs in flight

Test plan

  • Docs-only review of locked decisions
  • Confirm PR split A–D matches implementation appetite

@coderabbitai review

Lock layout, kit scaffold, OIDC/API-key auth, CP APIs, Stripe, and
PR split A–D for design §13 step 13; point HANDOVER at Plan 9.

Co-authored-by: Cursor <cursoragent@cursor.com>
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@cursor

cursor Bot commented Aug 1, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 36 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1d3e0f93-e466-4f5e-857a-7643deb25567

📥 Commits

Reviewing files that changed from the base of the PR and between 4409f9c and 37caa18.

📒 Files selected for processing (2)
  • docs/superpowers/plans/2026-08-01-plan-9-dashboard.md
  • docs/superpowers/plans/HANDOVER.md

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add Plan 9 dashboard doc and update HANDOVER pointer

📝 Documentation 🕐 10-20 Minutes

Grey Divider

AI Description

• Add Plan 9 doc locking dashboard architecture, auth, APIs, billing, and PR split A–D.
• Document constraints: Vite SPA at top-level dashboard/, CP-served static dist, OIDC PKCE.
• Update HANDOVER status and index to reference Plan 9 dashboard plan.
Diagram

graph TD
  U([User]) -->|loads SPA| CP["Control Plane (HTTP)"] -->|serves static| SPA["Dashboard SPA (Vite/React)"] -->|calls JSON APIs| CP --> DB[("Postgres")]
  SPA -->|OIDC PKCE| OIDC{{"Clerk OIDC"}}
  CP -->|checkout/portal| Stripe{{"Stripe"}} -->|webhook| CP
  subgraph Legend
    direction LR
    _svc["Service"] ~~~ _db[("Database")] ~~~ _ext{{"External"}} ~~~ _user(["User"])
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Nest dashboard under control-plane/ as a subpackage
  • ➕ Single root for CI/build tooling; fewer cross-package references
  • ➕ May simplify local dev if CP already owns frontend assets
  • ➖ Tightens coupling between UI and CP internals
  • ➖ Harder to keep SPA concerns (routing/auth/ui kit) isolated and reusable
  • ➖ Conflicts with the explicit decision to keep dashboard as a top-level package
2. Use Clerk SPA SDK/session libraries instead of generic OIDC
  • ➕ Faster UI auth implementation; more off-the-shelf session handling
  • ➕ Potentially less custom token plumbing
  • ➖ Adds vendor-specific runtime surface to the SPA
  • ➖ Less aligned with existing CP OIDC verification path and portability goals
  • ➖ Can complicate CI/e2e if live Clerk dependencies are required
3. Host dashboard as a separate web service/container
  • ➕ Clean separation of concerns; independent deploy cadence
  • ➕ Can scale UI separately from CP API
  • ➖ Introduces an additional production process and deployment surface area
  • ➖ Requires CORS/CSRF posture and environment wiring from day one
  • ➖ Overkill for the initial Plan 9 scope where CP already serves HTTP

Recommendation: The doc’s chosen approach (top-level dashboard/ Vite SPA, served as static files by the existing control plane, generic OIDC PKCE, and fixed-tier Stripe) is the best fit for Plan 9’s goal of shipping a dashboard with minimal new operational complexity. The main alternatives above are viable later, but they either increase coupling (nesting), add vendor lock-in/runtime risk (Clerk SDK), or expand infra surface area (separate service) ahead of demonstrated need.

Files changed (2) +135 / -5

Documentation (2) +135 / -5
2026-08-01-plan-9-dashboard.mdAdd Plan 9 dashboard locked decisions and implementation checklist +120/-0

Add Plan 9 dashboard locked decisions and implementation checklist

• Introduces the Plan 9 specification for the dashboard: locked architecture decisions (top-level Vite SPA, CP static serving), auth model (Clerk OIDC PKCE + CI API-key mode), required control-plane API surface, Stripe fixed-tier billing constraints, testing strategy, and a suggested PR split A–D with implementation tasks and risks.

docs/superpowers/plans/2026-08-01-plan-9-dashboard.md

HANDOVER.mdUpdate HANDOVER status and link Plan 9 dashboard plan +15/-5

Update HANDOVER status and link Plan 9 dashboard plan

• Updates the handover header (date/status/next session) and adds a Plan 9 section pointing to the new plan doc, plus adds Plan 9 to the document index table.

docs/superpowers/plans/HANDOVER.md

@qodo-code-review

qodo-code-review Bot commented Aug 1, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 18 rules

Grey Divider


Action required

1. Wrong staff field reference ✓ Resolved 🐞 Bug ⛨ Security
Description
Plan 9 states platform staff is derived from users.is_staff, but the control-plane derives
req.user.isStaff from users.platform_role (staff/admin). If implemented as written,
staff/admin gating will diverge from existing middleware and risks reintroducing authorization
mistakes around “staff vs tenant role” handling.
Code

docs/superpowers/plans/2026-08-01-plan-9-dashboard.md[30]

+4. **Auth:** Clerk via **generic OIDC** (Authorization Code + PKCE) in the SPA; API calls use `Authorization: Bearer <access_token>`. Reuse existing CP `authenticateOidc` / membership resolution. No Clerk session SDK in the SPA. Platform staff = `users.is_staff` (existing). Tenant context: memberships table; UI requires an active tenant selection when the user has multiple.
Relevance

●●● Strong

PR #6 accepted: derive req.user.isStaff from global platformRole, not tenant roles; doc’s
users.is_staff conflicts.

PR-#6

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Plan 9 doc explicitly names a non-existent/unused users.is_staff field, while the
control-plane schema and OIDC auth middleware use users.platform_role and derive
req.user.isStaff from it.

docs/superpowers/plans/2026-08-01-plan-9-dashboard.md[27-34]
control-plane/src/middleware/auth.ts[114-140]
control-plane/migrations/008_platform_role/migration.sql[3-7]
PR-#6

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Plan 9 doc claims platform staff is `users.is_staff`, but the actual control-plane data model and middleware use `users.platform_role` and compute `req.user.isStaff` from it.

## Issue Context
This is a “locked decision” doc that will be used as the implementation spec. Incorrect field naming here will lead to inconsistent authz checks (and potentially repeating earlier staff/tenant-role confusion).

## Fix
Update the Plan 9 doc to reference `users.platform_role` (values `staff`/`admin`) as the platform-staff signal, and explicitly note that tenant-scoped roles live in `tenant_memberships.role`.

## Fix Focus Areas
- docs/superpowers/plans/2026-08-01-plan-9-dashboard.md[27-35]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. No active tenant mechanism ✓ Resolved 🐞 Bug ≡ Correctness
Description
Plan 9 requires an “active tenant selection” for multi-tenant users while also saying to reuse the
existing authenticateOidc membership resolution, but the backend currently hard-codes
req.user.tenantId to the first membership and exposes no mechanism to select a tenant per request.
Implementing the dashboard against current semantics will read/write to an arbitrary tenant for
multi-tenant users.
Code

docs/superpowers/plans/2026-08-01-plan-9-dashboard.md[30]

+4. **Auth:** Clerk via **generic OIDC** (Authorization Code + PKCE) in the SPA; API calls use `Authorization: Bearer <access_token>`. Reuse existing CP `authenticateOidc` / membership resolution. No Clerk session SDK in the SPA. Platform staff = `users.is_staff` (existing). Tenant context: memberships table; UI requires an active tenant selection when the user has multiple.
Relevance

●●● Strong

PR #4/#6 discussions flag memberships[0]/no active-tenant selection as correctness/security risk;
team previously acted on it.

PR-#4
PR-#6

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The plan requires tenant selection, but current auth selects tenantId from memberships[0] and at
least some tenant-scoped routes use req.user.tenantId directly, meaning there is no way for a UI
tenant picker to affect backend scoping.

docs/superpowers/plans/2026-08-01-plan-9-dashboard.md[30-34]
control-plane/src/middleware/auth.ts[125-140]
control-plane/src/routes/principals.ts[18-25]
control-plane/src/lib/tenantFilter.ts[1-12]
PR-#6

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The plan mandates a UI tenant selector, but the control-plane currently chooses `req.user.tenantId` as the first membership and routes scope data using that value. Without a defined request contract, the UI selection cannot be honored.

## Issue Context
This is a spec/documentation mismatch that will cause real product behavior issues (wrong tenant shown/modified) for multi-tenant users.

## Fix
Update the Plan 9 doc to explicitly define how the dashboard selects the active tenant for each request (e.g., a required `X-Tenant-Id` header validated against the authenticated user’s memberships, or a dedicated “set active tenant” flow that yields a tenant-bound token). Also note that until this exists, the backend will default to the first membership and the UI cannot safely offer tenant switching.

## Fix Focus Areas
- docs/superpowers/plans/2026-08-01-plan-9-dashboard.md[27-35]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread docs/superpowers/plans/2026-08-01-plan-9-dashboard.md Outdated
Comment thread docs/superpowers/plans/2026-08-01-plan-9-dashboard.md Outdated
Match Decision 4 to users.platform_role and specify SPA active-tenant
via X-Tenant-Id validated against tenant_memberships.

Co-authored-by: Cursor <cursoragent@cursor.com>
@messagesgoel-blip

Copy link
Copy Markdown
Collaborator Author

Applied the two pre-merge adjustments (and matching Qodo threads):

  1. Platform staff = users.platform_role in (staff,admin) → req.user.isStaff
  2. Active tenant via X-Tenant-Id, validated against tenant_memberships in authenticateOidc

Ready to merge once CI is green.

@messagesgoel-blip
messagesgoel-blip merged commit 7730cb5 into main Aug 1, 2026
5 checks passed
@messagesgoel-blip
messagesgoel-blip deleted the docs/plan-9-dashboard branch August 1, 2026 05:39
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