Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 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
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
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
661 changes: 323 additions & 338 deletions frontend/bun.lock

Large diffs are not rendered by default.

Binary file added frontend/bun.lockb
Binary file not shown.
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