Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
149 commits
Select commit Hold shift + click to select a range
a0473a1
[P0] Add shadcn/ui foundation with Midnight Bloom tokens
hari-kuriakose Jul 26, 2026
26a8c64
[P1-01/P1-02] Migrate @ant-design/icons to lucide-react
hari-kuriakose Jul 26, 2026
612875b
[P1-03] Move Typography off antd via a compatibility shim
hari-kuriakose Jul 26, 2026
a0f3848
[P1-04] Move Button off antd via a compatibility shim
hari-kuriakose Jul 26, 2026
5146c8d
[P1] Formalise the shim convention; drop dead antd Button theme
hari-kuriakose Jul 26, 2026
63cb1d9
[P1-05] Move Space/Row/Col/Flex off antd via a layout shim
hari-kuriakose Jul 26, 2026
5d881cf
[P1-06] Move remaining leaf components off antd
hari-kuriakose Jul 26, 2026
c78c56f
[P2] Move overlays and notifications off antd
hari-kuriakose Jul 26, 2026
b915f4d
[P3-01..P3-03] Move Form and data-entry controls off antd
hari-kuriakose Jul 26, 2026
6e446e6
[P3-04..P4] Remove Ant Design entirely from the OSS frontend
hari-kuriakose Jul 27, 2026
5d8cc80
[Phase C] Add shared components the cloud plugins need
hari-kuriakose Jul 27, 2026
7310236
[P4-09 + gaps] Complete the CSS cleanup, shim tests, icon map and scr…
hari-kuriakose Jul 27, 2026
cda6a8a
[fix] Commit the lockfile changes from the antd removal
hari-kuriakose Jul 27, 2026
6509fcd
[fix] Layout and Sider shims dropped antd's sizing behaviour
hari-kuriakose Jul 27, 2026
d087bda
[fix] Detect Sider at runtime so Layout lays out as a row
hari-kuriakose Jul 27, 2026
9b9b329
[fix] Modal `centered` broke the dialog's centring transform
hari-kuriakose Jul 27, 2026
3e23562
[fix] List ignored antd's grid prop, stacking adapter cards
hari-kuriakose Jul 27, 2026
8437fd2
[fix] Tall modals overflowed the viewport with no scrollable body
hari-kuriakose Jul 27, 2026
9ed65b9
[fix] Emit the antd class names the app's own CSS still targets
hari-kuriakose Jul 27, 2026
8b05800
[fix] Collapse.Panel and Card.Meta were undefined, crashing Prompt St…
hari-kuriakose Jul 27, 2026
2c6bc45
[fix] Modal.useModal was undefined, crashing 12 components on click
hari-kuriakose Jul 27, 2026
990787d
[fix] App CSS `top: 20px` fought the Dialog's centring
hari-kuriakose Jul 27, 2026
0fe2cbf
[fix] Buttons dropped refs, leaving every Dropdown trigger inert
hari-kuriakose Jul 27, 2026
f250051
[fix] Add Dropdown.Button — the split-button shim was missing
hari-kuriakose Jul 27, 2026
8727ffd
[fix] RangePicker silently ignored five props its call-sites pass
hari-kuriakose Jul 27, 2026
8d8fe11
[fix] Rebuilding a caller's date via `new sample.constructor(iso)` is…
hari-kuriakose Jul 27, 2026
6e57570
[fix] Every dayjs-valued date field was rendering today's date
hari-kuriakose Jul 27, 2026
001cca5
[P3-04] Give RangePicker a real calendar popup
hari-kuriakose Jul 28, 2026
15228f3
[test] Pin the RangePicker against MetricsDashboard's real prop shape
hari-kuriakose Jul 28, 2026
ac7cc5d
[fix] Calendar popover stacked its months and ran off the screen
hari-kuriakose Jul 28, 2026
55b1b03
[fix] Declare dayjs — it was reaching the app only through antd
hari-kuriakose Jul 28, 2026
086daa0
[P4-08] Drop antd entirely — replace the cron picker, guard against i…
hari-kuriakose Jul 28, 2026
6226807
[fix] Four UI defects found comparing dev against the reference deplo…
hari-kuriakose Jul 28, 2026
a60fbd8
[fix] Replace hardcoded antd blues with design tokens
hari-kuriakose Jul 28, 2026
e80eb83
[fix] Loader centring and an unscrollable sidebar
hari-kuriakose Jul 28, 2026
5a3cd47
fix(frontend): unify primary colour on Midnight Bloom tokens
hari-kuriakose Jul 28, 2026
6c16bb3
fix(frontend): retire antd's default palette for Midnight Bloom tokens
hari-kuriakose Jul 28, 2026
57e3110
fix(frontend): restore input border colour, pointer cursors, dragger …
hari-kuriakose Jul 28, 2026
f7781af
test(frontend): guard the cascade-layer and cursor regressions
hari-kuriakose Jul 28, 2026
3b87e6c
fix(frontend): remove legacy .border rule so dashed borders render
hari-kuriakose Jul 28, 2026
3794dbc
fix(frontend): restore Form APIs that broke every create/edit modal
hari-kuriakose Jul 28, 2026
ded1310
style(frontend): set body text to 13px
hari-kuriakose Jul 28, 2026
732eb10
fix(frontend): even input borders and an uncut emoji picker
hari-kuriakose Jul 28, 2026
21942b0
refactor(frontend): move the antd shims to src/components/ui/shims/
hari-kuriakose Jul 29, 2026
76d22ea
refactor(frontend): consolidate on the radix-ui package
hari-kuriakose Jul 29, 2026
2d8c1b5
build(frontend): upgrade to React 19
hari-kuriakose Jul 29, 2026
202280d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 29, 2026
920e718
build(frontend): wire TypeScript into the Vite/Vitest transform pipeline
hari-kuriakose Jul 29, 2026
8909aba
test(frontend): keep the repo-wide guards scanning .tsx as well as .jsx
hari-kuriakose Jul 29, 2026
db2f025
refactor(frontend): convert Typography shim and tooltip primitive to …
hari-kuriakose Jul 29, 2026
1513189
refactor(frontend): convert 18 shadcn primitives to TypeScript
hari-kuriakose Jul 29, 2026
fc4230f
refactor(frontend): convert the remaining 13 primitives to TypeScript
hari-kuriakose Jul 29, 2026
f2b12d9
refactor(frontend): convert the layout and input shims to TypeScript
hari-kuriakose Jul 29, 2026
9e39bed
refactor(frontend): convert the Form shim to TypeScript
hari-kuriakose Jul 29, 2026
b682345
refactor(frontend): convert the leaf shims to TypeScript
hari-kuriakose Jul 29, 2026
b812f17
refactor(frontend): convert the date/time shims to TypeScript
hari-kuriakose Jul 29, 2026
3c18668
refactor(frontend): convert the overlay shims to TypeScript
hari-kuriakose Jul 29, 2026
2969d82
refactor(frontend): convert the structural shims to TypeScript
hari-kuriakose Jul 29, 2026
533305a
fix(frontend): accept the shim props the cloud plugins actually pass
hari-kuriakose Jul 29, 2026
65f8573
fix(frontend): regenerate bun.lock so the Docker build resolves
hari-kuriakose Jul 29, 2026
9a09fdc
fix(frontend): keep transforming JSX that lives in .js files
hari-kuriakose Jul 29, 2026
aa2816d
fix(frontend): use the automatic JSX runtime for JSX-in-.js
hari-kuriakose Jul 29, 2026
96bbead
fix(frontend): match the antd reference on Prompt Studio metrics
hari-kuriakose Jul 29, 2026
2a09fe5
fix(frontend): base font to 14px, icon sizes, list well, hover popovers
hari-kuriakose Jul 29, 2026
5497315
fix(frontend): forward hover props through Tooltip, widen scrollbar, …
hari-kuriakose Jul 29, 2026
992d674
fix(frontend): support standalone Radio, size the emoji picker to its…
hari-kuriakose Jul 30, 2026
7dcb48a
fix(frontend): render antd's line-style tabs, not shadcn's pill
hari-kuriakose Jul 30, 2026
ccbcb78
fix(frontend): size prompt-card icons, and detect this class of bug p…
hari-kuriakose Jul 30, 2026
f7bbfb0
fix(frontend): honour antd's controlled activeKey on the Collapse shim
hari-kuriakose Jul 30, 2026
19dc30a
fix(frontend): suppress the Collapse header bar for headerless panels
hari-kuriakose Jul 30, 2026
126dcf6
fix(frontend): fall back to the option value when a Select option has…
hari-kuriakose Jul 30, 2026
735853f
fix(frontend): treat an empty Select value as "nothing selected"
hari-kuriakose Jul 30, 2026
526ba06
fix(frontend): accept antd's `value` alias for Switch checked state
hari-kuriakose Jul 30, 2026
d30cc6e
feat(frontend): implement antd Select mode="tags" as a chip editor
hari-kuriakose Jul 30, 2026
f7853fc
fix(frontend): restrict the tag editor to mode="tags", consume Input …
hari-kuriakose Jul 30, 2026
ab3b266
fix(frontend): stop Custom Synonyms dropping typed characters
hari-kuriakose Jul 30, 2026
f47a0f0
fix(frontend): four Settings-modal layout mismatches against the refe…
hari-kuriakose Jul 30, 2026
b029874
fix(frontend): four more Settings mismatches — header, radio, active …
hari-kuriakose Jul 30, 2026
263c78c
fix(frontend): icon/text inline alignment, prompt card density, doc t…
hari-kuriakose Jul 30, 2026
25993c6
fix(frontend): TextArea swallowed antd's size, keeping shadcn's 60px …
hari-kuriakose Jul 30, 2026
cd51ab5
fix(frontend): antd-shaped table pager, and match its small-textarea …
hari-kuriakose Jul 30, 2026
e2f758d
fix(frontend): centre toolbar text by padding tab labels symmetrically
hari-kuriakose Jul 30, 2026
4095bd0
fix(frontend): drop tab label padding that pushed toolbar text off ce…
hari-kuriakose Jul 30, 2026
ba8057c
fix(frontend): centre the tab nav inside .tools-main-tabs
hari-kuriakose Jul 30, 2026
cf30adb
fix(frontend): centre the tab nav at both wrapper levels in the doc t…
hari-kuriakose Jul 30, 2026
67a8af3
fix(frontend): host confirm dialogs app-wide; forward Tooltip's ref t…
hari-kuriakose Jul 31, 2026
c1ffc34
fix(frontend): restore sidebar navigation, TopBar header, and menu hi…
hari-kuriakose Jul 31, 2026
3f4a512
fix(frontend): push dropdown item padding onto the label's own element
hari-kuriakose Jul 31, 2026
309ce2d
fix(frontend): stop the first fly-out entry looking selected; lift po…
hari-kuriakose Jul 31, 2026
2048442
fix(frontend): make Transfer the real antd widget; forward Select's s…
hari-kuriakose Jul 31, 2026
1abb503
fix(frontend): Tabs pane keys, remaining run-all icons, prompt-card i…
hari-kuriakose Jul 31, 2026
01f89e6
fix(frontend): strip the nested-array key prefix on Tabs panes too
hari-kuriakose Jul 31, 2026
317b1c4
fix(frontend): stop the workflow page growing a horizontal scrollbar
hari-kuriakose Jul 31, 2026
285f3ba
fix(frontend): stop DataTable looping on an inline rowSelection
hari-kuriakose Jul 31, 2026
e5e0e26
fix(frontend): render antd's preset Tag colours as tinted chips
hari-kuriakose Aug 1, 2026
ec127f2
fix(frontend): honour antd's responsive Col breakpoint props
hari-kuriakose Aug 1, 2026
5df9e21
fix(frontend): render the icon on Tabs items
hari-kuriakose Aug 1, 2026
1ef6069
fix(frontend): datepicker nav placement, year jump, and showTime labe…
hari-kuriakose Aug 1, 2026
808266d
fix(frontend): stop Tooltip clobbering its child's data-state
hari-kuriakose Aug 1, 2026
ba45894
fix(frontend): key Switch off aria-checked so a Tooltip cannot blank it
hari-kuriakose Aug 1, 2026
9222b3d
test(rig): wire the frontend vitest and playwright suites in as groups
hari-kuriakose Aug 1, 2026
31f8b4d
test(ui): cover every page visited during the shadcn migration
hari-kuriakose Aug 2, 2026
97c36be
test(ui): support both auth modes, colocate under tests/e2e/ui, drop …
hari-kuriakose Aug 2, 2026
4e4bb33
chore(frontend): drop the playwright leftovers from the frontend package
hari-kuriakose Aug 2, 2026
e155488
test(ui): replace the regression specs with functional ones
hari-kuriakose Aug 2, 2026
4f62faa
fix(frontend): add the Skeleton and Menu statics the cloud plugins re…
hari-kuriakose Aug 2, 2026
2c89c3c
build(frontend): regenerate bun.lock for the React 19 upgrades
hari-kuriakose Aug 2, 2026
afbc154
fix(frontend): restore antd's tab padding; add APS e2e coverage
hari-kuriakose Aug 2, 2026
a15d1c9
Merge origin/main into feat/shadcn-oss-migration
hari-kuriakose Aug 10, 2026
dc71188
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 10, 2026
5aa565d
fix(frontend): give ResourceTable's Tooltip an element child
hari-kuriakose Aug 10, 2026
c9562d1
fix(frontend): restore onRow and tableLayout on the table shim
hari-kuriakose Aug 11, 2026
e5824c5
feat(frontend): move the brand violet from the nav bars to the active…
jaseemjaskp Aug 19, 2026
8f40411
fix(frontend): honour showHeader on the table shim
jaseemjaskp Aug 20, 2026
cb20dfe
fix(frontend): make the Upload shim honour antd's `action` uploader
jaseemjaskp Aug 20, 2026
28ba203
Merge origin/main into feat/shadcn-oss-migration
jaseemjaskp Aug 20, 2026
cc03a9f
fix(frontend): carry option `disabled` through the Select shim
jaseemjaskp Aug 20, 2026
a3683c8
fix(frontend): restore the Select dropdown's max-height under Tailwin…
jaseemjaskp Aug 20, 2026
c0d1f9e
fix(frontend): show the error toast when API deployment creation fails
jaseemjaskp Aug 20, 2026
fa02b8c
feat(frontend): add a "Clear all" control for stacked toasts
jaseemjaskp Aug 20, 2026
ca1fed5
fix(frontend): restore antd's two-arg Switch onChange so card toggles…
jaseemjaskp Aug 20, 2026
4ac932f
fix(frontend): flatten fragments in the Space shim, as antd does
jaseemjaskp Aug 24, 2026
f334c4b
fix(frontend): restore banded headers and the scroll prop on the tabl…
jaseemjaskp Aug 24, 2026
20fa018
fix(frontend): render Descriptions as antd's table, not a stacked list
jaseemjaskp Aug 24, 2026
0766fc2
fix(frontend): stop Tailwind's container utility clamping the app shell
jaseemjaskp Aug 25, 2026
11891cd
fix(frontend): size the island against its flex parent, not 100%
jaseemjaskp Aug 25, 2026
d76cdbe
fix(frontend): give the Select shim back its search box and popup footer
jaseemjaskp Aug 26, 2026
abb04cf
style(frontend): apply biome formatting and import order
jaseemjaskp Aug 26, 2026
2bdd4fa
build: run biome in pre-commit CI, exempt tsconfig from check-json
jaseemjaskp Aug 26, 2026
5a8b6da
fix(frontend): connect the socket without a location-derived URL
jaseemjaskp Aug 26, 2026
d007118
docs(frontend): widen formatValue's JSDoc to the nulls it handles
jaseemjaskp Aug 26, 2026
4173472
build(tests): lock the e2e ui playwright version
jaseemjaskp Aug 26, 2026
a06f327
fix(frontend): give the select shim's click wrappers a11y roles
jaseemjaskp Aug 26, 2026
9784e05
fix(tests): point the ui e2e group at the frontend, not the backend
jaseemjaskp Aug 26, 2026
9373149
fix(frontend): put the modal layer above the app's legacy z-indexes
jaseemjaskp Aug 26, 2026
3606392
fix(frontend): let data-testid survive the antd shims
jaseemjaskp Aug 26, 2026
cee7b3c
feat(frontend): add data-testid to the critical-path controls
jaseemjaskp Aug 26, 2026
3aefd90
fix(tests): point the ui specs at test ids, not framework classes
jaseemjaskp Aug 26, 2026
d7d58d1
fix(frontend): stop bordered and mouseEnterDelay leaking onto the DOM
jaseemjaskp Aug 26, 2026
dcf6e82
feat(frontend): let the adapter, workflow and enforce-type pickers be…
jaseemjaskp Aug 26, 2026
ef719c7
fix(frontend): keep Layout and Segmented content inside their box
jaseemjaskp Aug 27, 2026
25b56ec
fix(frontend): honour the table's locale emptyText
jaseemjaskp Aug 27, 2026
bc7ee53
fix(frontend): let toasts be clicked through an open modal
jaseemjaskp Aug 27, 2026
41c817f
fix(frontend): give the shims back Tree.DirectoryTree and a working S…
jaseemjaskp Aug 27, 2026
bb9ceca
test(frontend): close the two holes that let #130 past the shim guard
jaseemjaskp Aug 27, 2026
543298e
fix(frontend): give the tags Select back its dropdown and its border
jaseemjaskp Aug 27, 2026
bdc71e9
fix(frontend): give Spin back its wrapper form, and size Tag's icon
jaseemjaskp Aug 27, 2026
f68bfa0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 27, 2026
4fec775
fix(frontend): give the PDF viewer its own default-layout styles
jaseemjaskp Aug 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ eggs/
.eggs/
lib/
lib64/
# The Python `lib/` rule above also matches the frontend's shadcn/ui helper
# directory (`@/lib/utils` per components.json). Keep that tracked.
!frontend/src/lib/
parts/
sdist/
var/
Expand Down
19 changes: 15 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ default_stages:
ci:
skip:
- hadolint-docker # Fails in pre-commit CI
- biome-check # Uses local npm/npx, not available in pre-commit CI
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
Expand All @@ -23,7 +22,9 @@ repos:
- id: check-docstring-first
- id: check-ast
- id: check-json
exclude: ".vscode/launch.json"
# Both files are JSONC — their tooling (VS Code, tsc) allows comments,
# but this hook parses with strict `json.loads`, which does not.
exclude: "(\\.vscode/launch\\.json|frontend/tsconfig\\.json)$"
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: check-toml
Expand Down Expand Up @@ -81,8 +82,18 @@ repos:
hooks:
- id: biome-check
name: biome check
entry: bash -c 'cd frontend && bunx @biomejs/biome check --write --files-ignore-unknown=true --no-errors-on-unmatched "${@#frontend/}"' --
language: system
# `language: node` lets pre-commit provision Biome itself, so this runs
# in pre-commit CI too instead of needing a local bun/npx.
#
# Not the upstream biomejs/pre-commit hook: that one runs from the repo
# root, but Biome's project root here is `frontend/` (where biome.json
# lives). Running from the root resolves the wrong package.json, which
# changes both `organizeImports` grouping and CSS formatting. Hence the
# `cd frontend` plus stripping the prefix off the paths pre-commit
# passes, which are relative to the repo root.
entry: bash -c 'cd frontend && biome check --write --files-ignore-unknown=true --no-errors-on-unmatched "${@#frontend/}"' --
language: node
additional_dependencies: ["@biomejs/biome@2.3.13"]
files: ^frontend/src/
types_or: [javascript, jsx, ts, tsx, json]

Expand Down
5 changes: 5 additions & 0 deletions frontend/biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
"files": {
"ignoreUnknown": false
},
"css": {
"parser": {
"tailwindDirectives": true
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
Expand Down
739 changes: 381 additions & 358 deletions frontend/bun.lock

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions frontend/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": false,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}
102 changes: 102 additions & 0 deletions frontend/docs/P0-FOUNDATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# P0 — shadcn/ui Foundation

Implements **P0-01 … P0-16** of `UN_SHADCN_IMPL_PLAN.md`
(spec: `UN_SHADCN_SPEC.md`, palette decision **D8** = Midnight Bloom).

**Zero visual change is the goal of this phase.** antd still renders every
screen; this PR only installs the shadcn stack alongside it (§7 coexistence).

---

## What landed

| Task | Change |
|---|---|
| P0-03/04 | shadcn stack + Radix + RHF/zod + self-hosted `@fontsource` Inter / Geist Mono |
| P0-05 | `components.json` (`style: new-york`, `tsx: false`, lucide) |
| P0-06 | `tailwind.config.js`, `postcss.config.js` |
| P0-07 | `@tailwindcss/vite` added **after** `optionalPluginImports()` |
| P0-08 | `src/lib/utils.js` — `cn()` |
| P0-09 | Legacy CSS vars → `--legacy-*` (D6) |
| P0-10/11/12 | `src/index.css`: Tailwind first, Midnight Bloom tokens, `@theme inline` |
| P0-13 | 32 primitives in `src/components/ui/` |
| P0-14 | `vitest.config.mjs`: `@` alias + `css: false` |
| P0-15 | `next-themes` `ThemeProvider` driven by the existing session theme |
| P0-16 | `sonner` `<Toaster />` + shared `useAppToast` helper |

## Gate results

| Gate | Result |
|---|---|
| **P0-G1** build w/o plugins | **PASS** — `src/plugins/` is absent in an OSS checkout by design (copy overlay, gitignored), so every build here exercises the `optionalPluginImports` path |
| **P0-G3** dark mode | **PASS** — see below |
| **P0-G4** lint | **PASS** (see "Pre-existing" note) |
| **P0-G5** tests | **PASS** — 4 files / 16 tests green |
| **P0-G6** no visual regression | **PASS** — see below |

### P0-G3 — `@theme inline` verified

Measured in headless Chromium by toggling `.dark` on `<html>`:

| | light | dark |
|---|---|---|
| `--background` | `#fafafa` | `#1a1a1a` |
| `--foreground` | `#1a1a1a` | `#fafafa` |
| **`bg-background` utility** | `rgb(250,250,250)` | `rgb(26,26,26)` |
| **`text-foreground` utility** | `rgb(26,26,26)` | `rgb(250,250,250)` |

The last two rows are the ones that matter: the **Tailwind utilities** flip, not
just the CSS variables. A plain `@theme` would have frozen them at the light
value — this is the trap called out in §2.5.3a.

`--primary` is `#6f5cef` in both modes (correct — Midnight Bloom keeps the
brand violet constant across themes).

### P0-G6 — no visual regression

Same page measured before and after P0:

| Metric | Before | After |
|---|---|---|
| antd elements | 33 | 33 |
| antd buttons | 5 | 5 |
| button height | 50px | 50px |
| button background | `rgb(47,147,246)` | `rgb(47,147,246)` |
| button radius | 6px | 6px |
| body background | `rgb(233,233,233)` | `rgb(233,233,233)` |
| body font | system | **Inter** |

Only the font changed — that is P0-12's intended effect. Tailwind Preflight does
not disturb antd, consistent with the coexistence test in §7.

---

## Notes for review

**Two deviations from the plan, both necessary and neither changing intent:**

1. **`jsconfig.json` added** (not in the plan). The shadcn CLI refuses to run
without it: *"Failed to load jsconfig.json. Couldn't find tsconfig.json"*. It
declares the same `@/*` → `./src/*` alias that `vite.config.js` already has,
so it also fixes editor/IDE resolution.

2. **`biome.json` — `css.parser.tailwindDirectives: true`** (not in the plan).
Without it Biome cannot parse `@theme` / `@plugin` / `@custom-variant` and
**fails CI with 4 CSS parse errors**. This is a required companion to
adopting Tailwind v4; the plan's P0-G4 gate missed it.

**Pre-existing lint findings (NOT introduced here):** `biome ci src/` reports 4
errors and 26 warnings. All 4 errors are in `src/assets/export-tool.svg` and
`src/assets/login-onboard-message.svg`; both files are byte-identical to `main`,
and running the same Biome binary against a pristine `main` checkout reproduces
them. The 26 warnings (`noEmptyBlockStatements`, `useBlockStatements`) are in
files this PR never touched.

**Deferred deliberately:**
- `body { overflow: hidden }` is left as-is. It interacts with Radix's
scroll-lock; P0-12 says re-test at the first P2 dialog conversion.
- `ALERT_SURFACE = "antd"` in `App.jsx` keeps antd as the single notification
surface. The sonner bridge is mounted and wired but dormant, so alerts are not
double-rendered. P2-06 flips the constant and deletes the antd branch.

**Not started:** P1–P4 and the cloud plugin phase, per the 🛑 STOP gate after P0.
98 changes: 98 additions & 0 deletions frontend/docs/form-pattern.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Form conversion pattern (P3-01)

The reviewed pattern that P3-02 applies to the remaining call-sites.

## The problem

antd's `Form` bundles validation, layout and state into one component, and this
codebase drives it **imperatively** through a form instance:

```jsx
const [form] = Form.useForm();
form.setFieldsValue({ name: group?.name }); // populate on edit
const values = await form.validateFields().catch(() => null);
if (!values) return; // bail out when invalid
form.resetFields();
```

14 `useForm()` sites and 102 `Form.Item`s depend on that API. Rewriting each by
hand onto raw react-hook-form would mean 102 independent chances to change
submit or validation behaviour — and one wrong `.catch()` silently lets an
invalid form submit.

## The pattern: keep antd's API, swap the engine

`@/components/ui/antd-form` implements antd's `Form` surface on top of
react-hook-form. Call-sites convert **by import only**:

```diff
-import { Form, Input } from "antd";
+import { Form } from "@/components/ui/antd-form";
+import { Input } from "@/components/ui/input";
```

The JSX is untouched:

```jsx
<Form form={form} layout="vertical">
<Form.Item
label="Name"
name="name"
rules={[{ required: true, message: "Group name is required" }]}
>
<Input maxLength={255} />
</Form.Item>
</Form>
```

## What the shim guarantees

| antd API | Behaviour preserved |
|---|---|
| `Form.useForm()` | Returns `[form]` with the imperative methods below |
| `form.setFieldsValue(obj)` | Populates fields — edit-mode modals depend on this |
| `form.getFieldsValue()` / `getFieldValue(n)` | Reads current values |
| `form.validateFields()` | **Rejects** when invalid, resolves with values when valid |
| `form.resetFields()` | Clears back to empty |
| `<Form.Item rules>` | `required`, `min`, `max`, `pattern`, and custom `validator` |
| `<Form.Item>` without `name` | Renders as plain layout |
| `onFinish` | Fires only when validation passes |

**The rejection semantics are the load-bearing part.** Call-sites are written as
`await form.validateFields().catch(() => null)` and bail on `null`. If
`validateFields` resolved on invalid input instead of rejecting, every one of
those guards would silently pass and submit bad data. Two unit tests pin this:
one asserts the rejection, one asserts `onFinish` does not fire while a required
field is empty.

## Rules translation

antd rule objects map onto RHF options:

| antd | RHF |
|---|---|
| `{ required: true, message }` | `required: message` |
| `{ max: n, message }` | `maxLength: { value: n, message }` |
| `{ min: n, message }` | `minLength: { value: n, message }` |
| `{ pattern: re, message }` | `pattern: { value: re, message }` |
| `{ validator: async fn }` | `validate: { customN: … }` — a thrown error's message becomes the inline message |

## Controlled-child injection

`Form.Item` clones its single child and injects `value`/`onChange`/`onBlur`,
which is how antd wires inputs too. `valuePropName="checked"` is honoured for
`Checkbox`/`Switch`. The child's own `onChange` still fires, so call-sites that
react to changes keep working.

## Reference conversion

`src/components/groups/GroupCreateEditModal.jsx` — a small modal exercising
`setFieldsValue` (edit mode), `validateFields().catch()` (submit guard),
`resetFields()` (cancel), and a `required` rule. Converted with an import change
only.

## When NOT to use the shim

New forms should use `react-hook-form` + `@/components/ui/form` directly. The
`antd-form` module exists to carry the existing 102 call-sites across without
behaviour drift, and should not grow new antd-only props.
Loading
Loading