Skip to content

Panel rewrite: design spec + P0.1 (@idento/ui foundation) - #59

Merged
thevladbog merged 21 commits into
mainfrom
docs/panel-rewrite-spec
Jul 13, 2026
Merged

Panel rewrite: design spec + P0.1 (@idento/ui foundation)#59
thevladbog merged 21 commits into
mainfrom
docs/panel-rewrite-spec

Conversation

@thevladbog

@thevladbog thevladbog commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • Design spec for the customer web panel rewrite (new stack: Vite + React 19 + TanStack Router/Query + Tailwind v4; new design from the Idento Panel board; shared @idento/ui package for panel/console/kiosk; RU+EN and light+dark as day-one requirements; AI-agent rules routed through the AGENTS.md chain, not Cursor).
  • Implementation plan for Phase P0.1 (workspace + @idento/ui foundation).
  • P0.1 implementation: packages/ui npm workspace package — design tokens (light+dark, board-1a values), Inter type ramp, base primitives (Button/Label/Input/Card), Radix-backed overlay primitives (Dialog/Sheet/DropdownMenu/Tooltip/Separator/Switch/Avatar), StatusPill, Skeleton/EmptyState, two-tier ConfirmDialog, AgentStatus + shared verdict vocabulary, public API barrel, ESLint import-boundary guard, and agent rules in packages/ui/AGENTS.md + root AGENTS.md.
  • Built task-by-task via subagent-driven development: implementer + independent reviewer per task (9/9 approved), plus a final whole-branch review (Ready to merge: Yes).

Notable follow-ups (not blocking, flagged for awareness)

  • Cross-platform discrepancy: mobile's Kotlin VerdictBand.AlreadyChecked currently renders Amber, while this package (per plan) maps already_checked_in to blue/info. Needs a deliberate reconciliation decision in a future task.
  • packages/ui/tsconfig.json's types array includes "node" project-wide rather than scoped to the two files that need it — low risk, worth narrowing later.

Test plan

  • npm test -w @idento/ui — 120/120 passing
  • npm run typecheck -w @idento/ui — clean
  • npm run lint -w @idento/ui (via direct npx eslint, the npm run wrapper hit an unrelated local tooling quirk) — 0 issues
  • Reviewer: confirm spec/plan match your intent before P0.2 (panel scaffold + shell + auth) begins

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a shared UI foundation with reusable buttons, forms, cards, dialogs, menus, tooltips, sheets, status indicators, empty states, and confirmation flows.
    • Added light and dark themes with semantic color tokens, typography utilities, and shared verdict styling.
    • Added agent connection status and status pill components.
    • Established workspace support for shared UI development.
  • Documentation

    • Added panel rewrite design plans and contributor guidance covering accessibility, localization, theming, and UI boundaries.
  • Tests

    • Added comprehensive component, theme, accessibility, and styling validation tests.

CI Bot and others added 17 commits July 13, 2026 22:48
Approved brainstorm outcome: Vite SPA stack (React 19 + TanStack
Router/Query), board winners 1c/1f/2c/4a/3e, full-stack phasing P0-P5,
console stays in web/ untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
packages/ui (npm workspace, source-imported) holds tokens + primitives +
verdict vocabulary for panel, console rewrite, and desktop kiosk; strict
content boundary (no i18n/api/feature code), react >=18 peer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…P0 requirements

Localization (EN+RU key parity, switcher), theming (light+dark,
token-only colors) and agent working rules (.cursor/rules/ui-package.mdc,
AGENTS.md, copilot-instructions.md) added to decisions log, foundation
section and P0 deliverables.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Cursor rule file

Root AGENTS.md section + scoped packages/ui/AGENTS.md and panel/AGENTS.md
instead of .cursor/rules/ui-package.mdc.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9 TDD tasks: workspace root, tokens (board 1a) with guard tests,
base primitives, board-1b kit (StatusPill, ConfirmDialog, EmptyState,
Skeleton, AgentStatus), verdict vocabulary, barrel + lint boundary +
AGENTS.md rules. P0.2 (panel/shell/auth) and P0.3 (openapi/CI/Docker)
get their own plans.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…aint

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…NTS.md chain

Barrel test-first per task-9 brief: index.test.ts asserts every public
export name resolves; index.ts re-exports cn/verdict + all Task 3-8
components. Added eslint.config.js blocking i18n/data-fetching/app
imports from @idento/ui. Added packages/ui/AGENTS.md scoped rules and
appended a Web frontend section to root AGENTS.md.

Also fixes two pre-existing gaps surfaced by running lint/typecheck for
the first time on this package: tsconfig's restricted "types" array
excluded @types/node (needed by theme.test.ts/no-hardcoded-colors.test.ts
for node:fs/path/url), and cn.test.ts tripped the new
no-constant-binary-expression rule.
Apply animate-spin class conditionally to the in_progress status icon,
ensuring the Loader2 spinner animates rather than appearing frozen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 13, 2026 21:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@thevladbog, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 34 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7102d43c-fb97-41a6-80a6-eaced7c3722a

📥 Commits

Reviewing files that changed from the base of the PR and between 1f1c8a1 and f71d1d0.

📒 Files selected for processing (18)
  • .github/labeler.yml
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .github/workflows/validate.yml
  • .gitignore
  • .superpowers/sdd/progress.md
  • docs/design-briefs/customer-web-panel.md
  • packages/ui/eslint.config.js
  • packages/ui/src/components/confirm-dialog.test.tsx
  • packages/ui/src/components/confirm-dialog.tsx
  • packages/ui/src/components/dialog.test.tsx
  • packages/ui/src/components/dialog.tsx
  • packages/ui/src/components/sheet.tsx
  • packages/ui/src/lib/cn.test.ts
  • packages/ui/src/lib/cn.ts
  • packages/ui/src/no-hardcoded-colors.test.ts
  • packages/ui/src/theme.css
  • packages/ui/src/theme.test.ts
📝 Walkthrough

Walkthrough

Introduces the @idento/ui workspace package, shared semantic themes and utilities, Radix-based UI primitives, domain components, public exports, tests, lint boundaries, and documentation for the customer panel rewrite.

Changes

Panel UI foundation

Layer / File(s) Summary
Rewrite architecture and workspace setup
AGENTS.md, docs/superpowers/..., package.json, packages/ui/package.json, packages/ui/tsconfig.json, packages/ui/vitest.config.ts, packages/ui/eslint.config.js
Documents panel rewrite boundaries and adds the workspace/package configuration, scoped rules, lint restrictions, and Vitest setup.
Theme and shared utilities
packages/ui/src/theme.css, packages/ui/src/theme.test.ts, packages/ui/src/lib/*, packages/ui/src/no-hardcoded-colors.test.ts
Adds light/dark semantic tokens, Tailwind mappings, typography utilities, cn, verdict vocabulary, and theme/color policy tests.
Basic UI primitives
packages/ui/src/components/{button,label,input,card}.*
Adds reusable Button, Label, Input, and Card components with variants, forwarded props, token-based styling, and tests.
Overlay and control primitives
packages/ui/src/components/{dialog,sheet,dropdown-menu,tooltip,separator,switch,avatar}.*
Adds Radix-based overlays and controls with styling wrappers, accessibility labels, ref forwarding, and interaction tests.
Domain components and public API
packages/ui/src/components/{status-pill,skeleton,empty-state,confirm-dialog,agent-status}.*, packages/ui/src/index.*
Adds status, loading, empty-state, typed-confirmation, and agent-status components, then exposes the package API and validates its exports.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: web

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: the panel rewrite design spec and the P0.1 @idento/ui foundation work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/panel-rewrite-spec

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@thevladbog

Copy link
Copy Markdown
Owner Author

@codex review

@socket-security

socket-security Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​radix-ui/​react-avatar@​1.2.2991007099100
Addednpm/​typescript-eslint@​8.64.01001007498100
Addednpm/​@​types/​node@​24.13.31001008196100
Addednpm/​tw-animate-css@​1.4.01001009486100
Addednpm/​eslint@​9.39.59710010097100

View full report

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
packages/ui/src/theme.css (1)

1-3: 🧹 Nitpick | 🔵 Trivial

Configure Stylelint to recognize Tailwind v4 at-rules.

Stylelint's scss/at-rule-no-unknown rule flags @source, @theme, and @utility as unknown at-rules. These are valid Tailwind v4 CSS directives. If Stylelint is part of the lint pipeline (npm run lint -w @idento/ui``), these false positives will cause failures. Add Tailwind v4 at-rules to the rule's ignoreAtRules list in the Stylelint configuration.

Also applies to: 64-64, 103-103, 109-109, 114-114, 119-119, 124-124, 129-129

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/theme.css` around lines 1 - 3, Update the Stylelint
configuration’s scss/at-rule-no-unknown rule to include Tailwind v4 directives
source, theme, and utility in its ignoreAtRules list, preserving the existing
ignored rules and lint behavior.

Source: Linters/SAST tools

packages/ui/src/no-hardcoded-colors.test.ts (1)

6-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Self-scan fragility: exempt this file's own filename.

EXEMPT doesn't include this file's own name, so walk() scans this test file against its own HEX/RGB regexes. It currently passes only because the regex literals don't happen to match their own patterns character-for-character — any future edit (e.g. adding an example color in a comment) could break this test for reasons unrelated to the policy it enforces.

♻️ Proposed fix
-const EXEMPT = new Set(["theme.css", "theme.test.ts"]);
+const EXEMPT = new Set(["theme.css", "theme.test.ts", "no-hardcoded-colors.test.ts"]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/no-hardcoded-colors.test.ts` around lines 6 - 22, Update the
EXEMPT set used by the no-hardcoded-colors test to include this test file’s own
filename, so walk(HERE) excludes it from HEX/RGB scanning while continuing to
scan all applicable non-exempt TypeScript, TSX, and CSS files.
packages/ui/src/theme.test.ts (1)

8-13: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inconsistent -foreground token coverage.

SEMANTIC_TOKENS includes --muted-foreground but omits the other -foreground companions (--card-foreground, --popover-foreground, --secondary-foreground, --accent-foreground, --destructive-foreground, --success-foreground, --warning-foreground, --info-foreground) that also exist in theme.css. Consider including all of them for consistent regression coverage, or drop --muted-foreground if the omission is intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/theme.test.ts` around lines 8 - 13, Update the
SEMANTIC_TOKENS list to include every existing -foreground companion from
theme.css, including card, popover, secondary, accent, destructive, success,
warning, and info, while retaining muted-foreground for consistent theme
regression coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ui/eslint.config.js`:
- Around line 10-14: Update the restricted-import patterns in the ESLint
configuration to use recursive globs that match arbitrarily deep subpaths for
`@tanstack` packages and panel, web, or desktop app imports. Preserve the existing
restrictions and messages while ensuring nested imports such as
`@tanstack/react-query/build/x` and ../panel/features/x are rejected.

In `@packages/ui/src/components/confirm-dialog.tsx`:
- Around line 9-21: Update ConfirmDialogProps to a discriminated union so
typedConfirmation and typedConfirmationLabel are required together: the
typed-confirmation variant must require both strings, while the
no-typed-confirmation variant must disallow them. Preserve the existing
rendering behavior, ensuring every rendered typed-confirmation Input always has
its corresponding label without introducing hardcoded fallback text.

---

Nitpick comments:
In `@packages/ui/src/no-hardcoded-colors.test.ts`:
- Around line 6-22: Update the EXEMPT set used by the no-hardcoded-colors test
to include this test file’s own filename, so walk(HERE) excludes it from HEX/RGB
scanning while continuing to scan all applicable non-exempt TypeScript, TSX, and
CSS files.

In `@packages/ui/src/theme.css`:
- Around line 1-3: Update the Stylelint configuration’s scss/at-rule-no-unknown
rule to include Tailwind v4 directives source, theme, and utility in its
ignoreAtRules list, preserving the existing ignored rules and lint behavior.

In `@packages/ui/src/theme.test.ts`:
- Around line 8-13: Update the SEMANTIC_TOKENS list to include every existing
-foreground companion from theme.css, including card, popover, secondary,
accent, destructive, success, warning, and info, while retaining
muted-foreground for consistent theme regression coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 656e8235-3589-4454-8fc7-9c7c3646c718

📥 Commits

Reviewing files that changed from the base of the PR and between 49947d2 and 1f1c8a1.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (43)
  • AGENTS.md
  • docs/superpowers/plans/2026-07-13-panel-p0.1-workspace-ui-foundation.md
  • docs/superpowers/specs/2026-07-13-panel-rewrite-design.md
  • package.json
  • packages/ui/AGENTS.md
  • packages/ui/eslint.config.js
  • packages/ui/package.json
  • packages/ui/src/components/agent-status.test.tsx
  • packages/ui/src/components/agent-status.tsx
  • packages/ui/src/components/avatar.tsx
  • packages/ui/src/components/button.test.tsx
  • packages/ui/src/components/button.tsx
  • packages/ui/src/components/card.tsx
  • packages/ui/src/components/confirm-dialog.test.tsx
  • packages/ui/src/components/confirm-dialog.tsx
  • packages/ui/src/components/dialog.test.tsx
  • packages/ui/src/components/dialog.tsx
  • packages/ui/src/components/dropdown-menu.test.tsx
  • packages/ui/src/components/dropdown-menu.tsx
  • packages/ui/src/components/empty-state.test.tsx
  • packages/ui/src/components/empty-state.tsx
  • packages/ui/src/components/input.test.tsx
  • packages/ui/src/components/input.tsx
  • packages/ui/src/components/label.tsx
  • packages/ui/src/components/separator.tsx
  • packages/ui/src/components/sheet.tsx
  • packages/ui/src/components/skeleton.tsx
  • packages/ui/src/components/status-pill.test.tsx
  • packages/ui/src/components/status-pill.tsx
  • packages/ui/src/components/switch.tsx
  • packages/ui/src/components/tooltip.tsx
  • packages/ui/src/index.test.ts
  • packages/ui/src/index.ts
  • packages/ui/src/lib/cn.test.ts
  • packages/ui/src/lib/cn.ts
  • packages/ui/src/lib/verdict.test.ts
  • packages/ui/src/lib/verdict.ts
  • packages/ui/src/no-hardcoded-colors.test.ts
  • packages/ui/src/test/setup.ts
  • packages/ui/src/theme.css
  • packages/ui/src/theme.test.ts
  • packages/ui/tsconfig.json
  • packages/ui/vitest.config.ts

Comment thread packages/ui/eslint.config.js
Comment thread packages/ui/src/components/confirm-dialog.tsx Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f1c8a1113

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/ui/eslint.config.js Outdated
patterns: [
{ group: ["i18next", "i18next/*", "react-i18next"], message: "@idento/ui is i18n-agnostic — take strings via props." },
{ group: ["axios", "openapi-fetch", "@tanstack/*"], message: "@idento/ui must not fetch data or route." },
{ group: ["**/panel/*", "**/web/*", "**/desktop/*"], message: "@idento/ui must not import from apps." },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Widen the app-import restriction globs

When a future @idento/ui file reaches into app source via a normal relative path such as ../../web/src/lib/api or ../../panel/src/..., these patterns do not match because the * after web/panel/desktop only covers a single path segment. That lets npm run lint -w @idento/ui allow the app dependency that this rule is meant to block; use recursive globs like **/web/**, **/panel/**, and **/desktop/** (or equivalent) so deeper app paths are rejected.

Useful? React with 👍 / 👎.

Comment thread packages/ui/src/lib/cn.ts
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve custom type utilities when merging classes

The shared cn helper runs every class list through tailwind-merge, but tailwind-merge's default config does not know that the new @utility text-body, text-card-title, etc. are font-size utilities. When a primitive combines them with semantic text colors, such as buttonVariants producing text-body ... text-primary-foreground or DialogDescription using text-body text-muted-foreground, the merge treats the custom type utility as a text-color class and drops it, so the Inter type ramp silently disappears across most components. Configure tailwind-merge for these custom text utilities or avoid merging them against text-color classes.

Useful? React with 👍 / 👎.

</DialogHeader>
{typedConfirmation !== undefined ? (
<div className="flex flex-col gap-2">
{typedConfirmationLabel ? <Label htmlFor={inputId}>{typedConfirmationLabel}</Label> : null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Require an accessible label for typed confirmations

When typedConfirmation is set but the caller omits typedConfirmationLabel (which the exported props currently allow), this branch still renders the confirmation input with only an id and no accessible name. Screen reader users then cannot tell what value must be entered before a destructive action is enabled; make the label required for typed confirmations or provide an aria-label/fallback tied to the required phrase.

Useful? React with 👍 / 👎.


describe("no hardcoded colors outside theme.css", () => {
const files = walk(HERE).filter(
(f) => !EXEMPT.has(f.split("/").pop()!) && /\.(ts|tsx|css)$/.test(f),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use path basename for color-test exemptions

On Windows, join() produces backslash-separated paths, so f.split("/").pop() leaves the whole path and the theme.css / theme.test.ts exemptions never match. Running npm test -w @idento/ui there will scan theme.css and fail on the intentionally allowed palette hex values; use basename(f) from node:path instead of splitting on /.

Useful? React with 👍 / 👎.

Comment thread packages/ui/src/components/dialog.tsx Outdated
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn("fixed inset-0 z-50 bg-foreground/40 data-[state=open]:animate-in data-[state=open]:fade-in-0", className)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Use a dark scrim token for overlays

In the .dark theme, foreground is the light text color (#f4f4f5), so opening a Dialog or Sheet applies a translucent light overlay instead of dimming the page. That makes dark-mode overlays wash out the background rather than providing a proper modal scrim; add a dedicated overlay/backdrop token (or equivalent semantic class) that stays dark enough in both themes.

Useful? React with 👍 / 👎.

CI Bot and others added 2 commits July 14, 2026 00:39
- cn(): extendTailwindMerge with a custom font-size class group so the
  Inter type-ramp utilities (text-body, text-card-title, ...) no longer
  get silently dropped when merged with a text-color class — this was
  breaking typography on nearly every component (button, dialog, card,
  input, label, tooltip, empty-state, agent-status, avatar).
- ConfirmDialog: typedConfirmation/typedConfirmationLabel are now a
  discriminated union so the typed-confirm input can never render
  without an accessible label.
- eslint.config.js: widen no-restricted-imports globs to ** so nested
  subpaths (@tanstack/react-query/build/x, ../panel/features/x) are
  actually caught, not just one path segment deep.
- no-hardcoded-colors.test.ts: use path.basename() instead of
  splitting on "/" so the theme.css/theme.test.ts exemption still
  matches on Windows.
- theme.css: add an --overlay token (same dark value in both themes)
  and use it for Dialog/Sheet backdrops instead of bg-foreground,
  which washed out to a light overlay in dark mode.

Regression tests added for all five fixes; full suite green (124/124),
typecheck and lint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- labeler.yml: new "panel" (panel/**, not created yet — P0.2) and
  "shared-ui" (packages/**) labels; both created in the repo.
- ci.yml: detect packages/**, package.json, package-lock.json, .npmrc
  changes as "shared-ui" and pass it into validate.yml.
- validate.yml: new run-shared-ui input gating typecheck-shared-ui,
  lint-shared-ui, test-shared-ui — same shape as the existing web
  jobs, but installing via root npm workspace (npm ci at repo root,
  npm run <script> -w @idento/ui).
- release.yml: run-shared-ui: true alongside the existing unconditional
  backend/web validation on tag pushes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the ci label Jul 13, 2026
CI Bot and others added 2 commits July 14, 2026 00:42
The panel-rewrite design spec cites this as its primary input
(docs/superpowers/specs/2026-07-13-panel-rewrite-design.md, "Inputs:")
but it was never committed — the reference was broken.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Never committed, no history — contains launch.json plus per-session
worktree scratch (232MB found locally). Same treatment as .idea/ and
.vscode/* already in this file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thevladbog
thevladbog merged commit 8729315 into main Jul 13, 2026
28 checks passed
@thevladbog
thevladbog deleted the docs/panel-rewrite-spec branch July 13, 2026 21:46
thevladbog added a commit that referenced this pull request Jul 14, 2026
* docs: implementation plan P0.2 — panel scaffold, shell & auth

11 TDD tasks: panel/ workspace scaffold, Tailwind v4 wired to
@idento/ui/theme.css (verified via real build, not jsdom), TanStack
Router (code-based) + Query, i18n EN/RU with a key-parity guard,
light/dark ThemeProvider, session adapter + minimal fetch client
against the real (not stale-openapi) backend contracts, auth screens
(login/register SaaS-gated/QR staff), app shell (nav, org switcher,
edition-aware ON-PREM tag, mobile drawer), suspended-tenant takeover
driven by a global QueryCache error handler, impersonation banner
ported from the console's proven behavior, panel/AGENTS.md.

P0.1 (@idento/ui) merged via PR #59. P0.3 (openapi client + CI/Docker)
gets its own plan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(panel): npm workspace scaffold — Vite boots, App renders

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(panel): restore @types/react to the brief's ^19.2.7 (only @types/react-dom needed the ^19.0.0 downgrade)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(panel): wire Tailwind v4 to @idento/ui/theme.css, self-host Inter

* feat(panel): TanStack Router + Query providers, code-based route tree

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(panel): i18n EN/RU with key-parity guard, LanguageSwitcher

* feat(panel): light/dark ThemeProvider + ThemeSwitcher

* fix(panel): dark: variant tracks the .dark class not the OS media query; centralize matchMedia test stub

- Remap Tailwind's dark: variant in panel/src/app/styles.css via
  @custom-variant dark (&:where(.dark, .dark *)) so ThemeSwitcher's
  Sun/Moon icons follow the app's actual .dark class toggle instead of
  the OS prefers-color-scheme media query (Finding 1).
- Add a global window.matchMedia stub to panel/src/test/setup.ts,
  imported vi from "vitest" explicitly so it type-checks under
  tsconfig.app.json (which lacks vitest/globals types), and remove the
  now-redundant per-file copies from ThemeProvider.test.tsx,
  ThemeSwitcher.test.tsx, and App.test.tsx (Finding 2).

* fix(panel): suppress react-refresh/only-export-components for the ThemeProvider+useTheme co-location pattern

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(panel): session adapter, minimal fetch client, useInstance

Session module owns the localStorage keys (token/user/tenants/current_tenant)
that web/ already uses in production. Client wraps fetch for the real backend
endpoints (auth/login, auth/register, auth/login-qr, api/instance,
api/auth/switch-tenant) verified against backend/internal/handler/*.go, and
throws ApiError on non-2xx responses. useInstance is a thin TanStack Query
wrapper around getInstance with staleTime: Infinity.

Dropped one unused `ApiError` import from client.test.ts (copy-paste
leftover in the brief's test — the failure-mode assertion uses
rejects.toMatchObject, not toBeInstanceOf) to satisfy
@typescript-eslint/no-unused-vars.

* feat(panel): auth screens — Login, Register (SaaS-only, route-guarded), QR staff login

* fix(panel): clear stale current_tenant on QR login; use router Link for in-app auth-screen navigation

- session.ts: saveSession() now removes the stored current_tenant when the
  new session has none to set (QR login), instead of leaving a stale value
  from a previous session in localStorage — fixes a cross-session tenant
  leak on shared devices.
- LoginScreen/RegisterScreen: replace raw <a href> in-app links with
  TanStack Router's Link so navigation between /login, /register, and
  /qr-login is client-side instead of a full page reload.
- LoginScreen.test.tsx/RegisterScreen.test.tsx: wrap renders in a minimal
  RouterContextProvider so Link has the router context it now needs;
  without this the components crash on render (useLinkProps dereferences
  a null router outside a router context).

* fix(docs): P0.2 plan — Task 8's router.tsx rewrite must keep Task 7's /register guard

Task 8 replaces router.tsx wholesale to nest routes under a protected
layout route; the plan's code block for that rewrite had silently
dropped the beforeLoad edition-guard Task 7 just implemented and got
reviewed/approved. Restored it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(panel): app shell — nav, org switcher, mobile drawer, session guard

* fix(panel): wire OrgSwitcher tenant-count and AppShell ON-PREM tag through i18n

Both strings shipped as hardcoded English, violating the plan's binding
"RU + EN from the first screen" constraint. OrgSwitcher's tenant count now
uses react-i18next's CLDR pluralization (orgSwitcherCount_one/_few/_many/_other)
since Russian has different plural rules than English. AppShell's ON-PREM
version tag is now routed through an onPremVersionTag key (kept in Latin
script per convention, but now localizable without a code change).

keyParity.test.ts previously asserted en.json and ru.json have identical key
sets, which is incompatible with CLDR pluralization (en has 2 plural forms,
ru has 4). Rescoped it to require exact parity for non-pluralized keys and
family-level parity (base name presence, "other" fallback required) for
pluralized keys, preserving the original guarantee's strength everywhere it
still applies.

* fix(panel): wire NavDrawer's aria-label and closeLabel through i18n

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(panel): wire the Home placeholder route through i18n

Extracts the index route's inline component into a named HomePlaceholder
function component so useTranslation() can be called correctly, and
routes the hardcoded "Signed in — Home ships in P1." string through a
new homeComingSoon i18n key (EN + RU), closing the last hardcoded-string
gap in Task 8's shell/router code.

* feat(panel): suspended-tenant takeover, driven by a global QueryCache error handler

* fix(panel): correct support-email domain on the suspended-tenant screen (idento.io -> idento.app)

* docs: fix support-email domain in P0.2 plan (idento.io -> idento.app)

* feat(panel): impersonation banner, ported from the console's proven behavior

* fix(panel): getImpersonation() restores operator token on expiry, self-heals on malformed JSON

Fixes two review findings on Task 10's impersonation banner:
- The expiry branch only removed the session key, leaking operator_token
  forever and leaving token pointed at the expired impersonation JWT. Now
  reuses endImpersonation() (already correct) instead of duplicating logic.
- JSON.parse on the stored session was unguarded; malformed localStorage
  data crashed the render tree. Now wrapped in try/catch, self-healing by
  clearing the key and returning null, matching web/src/lib/impersonation.ts.

* docs: fix P0.2 plan's getImpersonation() to match proven web/ behavior (restore operator token on expiry, self-heal on malformed JSON)

* docs(panel): scoped agent rules + link from root AGENTS.md

* fix(panel): correct AGENTS.md's session rule to acknowledge the impersonation exception

* docs: fix P0.2 plan's AGENTS.md session-rule text to acknowledge the impersonation exception

* fix(panel): wire ThemeSwitcher and LanguageSwitcher aria-labels through i18n

Fixes hardcoded English aria-label values that bypassed i18n on the theme
and language switcher buttons. Russian-locale screen-reader users now hear
localized labels matching the buttons' locale context. Establishes these
icon-only buttons' aria-labels within the i18n scope, consistent with the
fix applied to NavDrawer.tsx in Task 8.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs: sync P0.2 plan text with already-shipped fixes (saveSession clears stale current_tenant, auth screens use router Link)

PR review (CodeRabbit) correctly flagged that the plan document's
LoginScreen/RegisterScreen/session.ts code blocks still showed the
pre-fix versions, even though the actual shipped code was corrected
during Task 7/8's review rounds. No functional change — code was
already correct; only the plan's historical snapshot was stale.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(panel): close PR #60 review findings — session, routing, i18n

Ten confirmed bugs from Codex/CodeRabbit review of PR #60 (panel P0.2):

- saveSession no longer leaves a stale impersonation/operator_token pair
  behind on a fresh login, which could restore a parked operator session
  over the new user's real one.
- The /register route guard now fails closed (redirects to /login) when
  the instance lookup rejects, instead of surfacing an unhandled error
  state that bypassed the SaaS-only guard.
- QR login code is trimmed before submission so trailing whitespace from
  copy/paste no longer breaks the exact-match backend lookup.
- queryClient now wires a MutationCache alongside the QueryCache so a
  tenant_suspended error from a mutation (e.g. OrgSwitcher's tenant
  switch) triggers the suspended-tenant takeover, not just query errors.
- A global 401 handler clears the stored session and redirects to
  /login on any protected-call session expiry, while explicitly skipping
  the three auth screens' own login/register/QR mutations (tagged via
  mutationKey) since their 401s are expected wrong-credential responses
  handled inline.
- AppShell's "Events" nav link uses activeOptions={{ exact: true }} so it
  no longer shows active on every route (TanStack Router treats "/" as a
  prefix match by default).
- Auth screens render localized error copy (authErrorKey.ts + new en/ru
  i18n keys) instead of raw backend/network text.
- getCurrentUser/getTenants/getCurrentTenant self-heal on malformed
  localStorage JSON instead of throwing during ProtectedLayout render.
- <html lang> now syncs with the active i18n language via a
  languageChanged listener.
- impersonationSession's malformed-JSON catch path now calls
  endImpersonation() (matching the already-correct expiry branch) so a
  corrupted session record can't leave the operator token stranded.

Verified un-fixed: panel/index.html's hardcoded theme-color meta tag
matches web/index.html's existing precedent — no change needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* ci: wire panel/ into labeler-adjacent CI (typecheck/lint/test/build)

The panel/** path was never added to ci.yml's change-detection filter,
so every commit in this PR — including the review-fix batch that
added a global 401 handler and a MutationCache — has been merging
with CI showing green while zero panel-specific verification (tests,
typecheck, lint, build) actually ran. Only "Shared UI" checks fired,
and only incidentally, because Task 1's workspace registration
touched the root package.json/package-lock.json (which the existing
shared-ui filter watches).

- ci.yml: detect panel/**, package.json, package-lock.json, .npmrc
  changes as "panel" and pass it into validate.yml as run-panel.
- validate.yml: new run-panel input gating typecheck-panel,
  lint-panel, test-panel, build-panel — same shape as the existing
  shared-ui jobs, installing via root npm workspace.
- release.yml: run-panel: true alongside the existing unconditional
  backend/web/shared-ui validation on tag pushes.

labeler.yml already had a panel: panel/** rule from an earlier phase
(unused until now since nothing referenced panel/ in CI) — no change
needed there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: CI Bot <ci@example.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants