fix(canvas): align tier text contracts with 4-tier reality (T1/T2/T3/T4)

The tier system in CreateWorkspaceDialog and design-tokens has been
T1 Sandboxed / T2 Standard / T3 Privileged / T4 Full Access, but two
chrome surfaces still showed the older 3-tier mapping with T3 as
"Full Access":

- Legend (bottom-left chrome on every canvas page) listed only T1/T2/T3
  and called T3 "Full Access". On a SaaS tenant the actual workspace
  badges render T4 (in amber/warm) — there was no T4 entry in the
  legend at all, so the user sees an undocumented orange badge.

- ConfigTab tier dropdown (per-workspace settings → Sandboxing) had no
  T4 option at all and called T3 "Full Access". So an existing T4
  workspace would show "T3 — Full Access" as the selected option,
  silently downgrading the displayed tier on the settings panel.

- tenant.ts isSaaSTenant() doc comment claimed SaaS workspaces are
  "inherently T3 Full Access" — wrong on both the number and the lock
  rationale (SaaS hides T1/T2/T3, not just T1/T2).

Fix:
- Legend now imports TIER_CONFIG and renders all four tiers
  (Sandboxed/Standard/Privileged/Full Access) using the same color
  swatches as the badges on workspace cards. Eliminates the previous
  drift where Legend's hardcoded sky/violet/warm chips didn't match
  the gray/sky/violet/amber actually rendered on nodes.
- ConfigTab adds the missing T4 — Full Access option and renames T3
  to Privileged.
- tenant.ts comment updated to match the picker's actual hide list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hongming Wang 2026-05-03 05:56:18 -07:00
parent dc6425fe39
commit 7abb94dab8
3 changed files with 20 additions and 7 deletions

View File

@ -1,11 +1,23 @@
"use client";
import { useEffect, useState } from "react";
import { STATUS_CONFIG } from "@/lib/design-tokens";
import { STATUS_CONFIG, TIER_CONFIG } from "@/lib/design-tokens";
import { useCanvasStore } from "@/store/canvas";
const LEGEND_STATUSES = ["online", "provisioning", "degraded", "failed", "paused", "offline"] as const;
// Tier descriptions kept in sync with CreateWorkspaceDialog.tsx (the
// source of truth for what each tier means semantically). Colors come
// from TIER_CONFIG so the legend swatch matches the badge actually
// rendered on every WorkspaceNode — drift here misled users into
// thinking the legend documented a different tier than the one shown.
const LEGEND_TIERS: ReadonlyArray<{ tier: number; label: string }> = [
{ tier: 1, label: "Sandboxed" },
{ tier: 2, label: "Standard" },
{ tier: 3, label: "Privileged" },
{ tier: 4, label: "Full Access" },
];
// Persist the user's choice across sessions. Default is "open" so
// first-time users still see the symbol key; once dismissed we
// respect that until they explicitly reopen via the floating pill.
@ -102,9 +114,9 @@ export function Legend() {
<div className="mb-2">
<div className="text-[11px] text-ink-soft font-medium mb-1">Tier</div>
<div className="flex flex-wrap gap-x-3 gap-y-1">
<TierItem tier={1} label="Sandboxed" color="text-sky-300 bg-sky-950/40 border-sky-700/30" />
<TierItem tier={2} label="Standard" color="text-violet-300 bg-violet-950/40 border-violet-700/30" />
<TierItem tier={3} label="Full Access" color="text-warm bg-amber-950/40 border-amber-700/30" />
{LEGEND_TIERS.map(({ tier, label }) => (
<TierItem key={tier} tier={tier} label={label} color={TIER_CONFIG[tier].border} />
))}
</div>
</div>

View File

@ -655,7 +655,8 @@ export function ConfigTab({ workspaceId }: Props) {
>
<option value={1}>T1 Sandboxed</option>
<option value={2}>T2 Standard</option>
<option value={3}>T3 Full Access</option>
<option value={3}>T3 Privileged</option>
<option value={4}>T4 Full Access</option>
</select>
</div>
</div>

View File

@ -59,8 +59,8 @@ export function getTenantSlug(): string {
* isSaaSTenant reports whether the canvas is running as the UI for a
* SaaS tenant (served at <slug>.moleculesai.app). Use for client-side
* UX branches that should behave differently on SaaS vs self-hosted
* e.g. the workspace tier picker hides T1/T2 sandbox tiers because every
* SaaS workspace gets its own EC2 VM (inherently T3 Full Access).
* e.g. the workspace tier picker hides T1/T2/T3 sandbox tiers because
* every SaaS workspace gets its own EC2 VM (inherently T4 Full Access).
*
* SSR-safe: returns false on the server to avoid hydration drift; call
* sites should tolerate a flip from falsetrue on first client render.