From 7be99058fd2eca7ccef0b6a5614bc9fa675f6d91 Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Sat, 16 May 2026 19:03:12 -0400 Subject: [PATCH 01/12] chore(plans): mark authoring-screens in-progress --- plans/authoring-screens.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plans/authoring-screens.md b/plans/authoring-screens.md index 009a8c1..3ae0272 100644 --- a/plans/authoring-screens.md +++ b/plans/authoring-screens.md @@ -1,5 +1,5 @@ --- -status: planned +status: in-progress depends: [public-screens, write-api] specs: - specs/screens/project-edit.md From 2847f44869756e445250b3fbcda6df323d2b1a3c Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Sat, 16 May 2026 19:03:54 -0400 Subject: [PATCH 02/12] chore(web): add react-hook-form, hookform resolvers, sonner, zod npm install -w apps/web react-hook-form @hookform/resolvers sonner zod --- apps/web/package.json | 6 +++++- package-lock.json | 50 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 4924d10..42c3189 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@fontsource-variable/geist": "^5.2.8", + "@hookform/resolvers": "^5.2.2", "@tailwindcss/vite": "^4.3.0", "@tanstack/react-query": "^5.100.10", "class-variance-authority": "^0.7.1", @@ -31,10 +32,13 @@ "radix-ui": "^1.4.3", "react": "^19.2.6", "react-dom": "^19.2.6", + "react-hook-form": "^7.76.0", "react-router": "^7.15.1", "shadcn": "^4.7.0", + "sonner": "^2.0.7", "tailwind-merge": "^3.6.0", "tailwindcss": "^4.3.0", - "tw-animate-css": "^1.4.0" + "tw-animate-css": "^1.4.0", + "zod": "^4.4.3" } } diff --git a/package-lock.json b/package-lock.json index b273a60..4cc5ef7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "version": "0.0.0", "dependencies": { "@fontsource-variable/geist": "^5.2.8", + "@hookform/resolvers": "^5.2.2", "@tailwindcss/vite": "^4.3.0", "@tanstack/react-query": "^5.100.10", "class-variance-authority": "^0.7.1", @@ -67,11 +68,14 @@ "radix-ui": "^1.4.3", "react": "^19.2.6", "react-dom": "^19.2.6", + "react-hook-form": "^7.76.0", "react-router": "^7.15.1", "shadcn": "^4.7.0", + "sonner": "^2.0.7", "tailwind-merge": "^3.6.0", "tailwindcss": "^4.3.0", - "tw-animate-css": "^1.4.0" + "tw-animate-css": "^1.4.0", + "zod": "^4.4.3" }, "devDependencies": { "@testing-library/jest-dom": "^6.9.1", @@ -2437,6 +2441,18 @@ "hono": "^4" } }, + "node_modules/@hookform/resolvers": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", + "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -4750,6 +4766,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@tailwindcss/node": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", @@ -12106,6 +12128,22 @@ "react": "^19.2.6" } }, + "node_modules/react-hook-form": { + "version": "7.76.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.76.0.tgz", + "integrity": "sha512-eKtLGgFeSgkHqQD8J59AMZ9a4uD1D83iSIzt4YlTGD7liDen5rrjcUO1rVIGd9yC1gofryjtHbv+4ny4hkLWlw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -13031,6 +13069,16 @@ "atomic-sleep": "^1.0.0" } }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/sort-keys": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-6.0.0.tgz", From 33e182a8c2a09b6e7dd9c6c58a56f9ffd42fd8b0 Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Sat, 16 May 2026 19:04:09 -0400 Subject: [PATCH 03/12] chore(web): add shadcn select, textarea, label, checkbox npx shadcn@latest add select textarea label checkbox --yes --cwd apps/web --- apps/web/src/components/ui/checkbox.tsx | 31 ++++ apps/web/src/components/ui/label.tsx | 24 +++ apps/web/src/components/ui/select.tsx | 190 ++++++++++++++++++++++++ apps/web/src/components/ui/textarea.tsx | 18 +++ 4 files changed, 263 insertions(+) create mode 100644 apps/web/src/components/ui/checkbox.tsx create mode 100644 apps/web/src/components/ui/label.tsx create mode 100644 apps/web/src/components/ui/select.tsx create mode 100644 apps/web/src/components/ui/textarea.tsx diff --git a/apps/web/src/components/ui/checkbox.tsx b/apps/web/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..cec7a77 --- /dev/null +++ b/apps/web/src/components/ui/checkbox.tsx @@ -0,0 +1,31 @@ +import * as React from "react" +import { Checkbox as CheckboxPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { CheckIcon } from "lucide-react" + +function Checkbox({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ) +} + +export { Checkbox } diff --git a/apps/web/src/components/ui/label.tsx b/apps/web/src/components/ui/label.tsx new file mode 100644 index 0000000..1ac80f7 --- /dev/null +++ b/apps/web/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +"use client" + +import * as React from "react" +import { Label as LabelPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Label({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Label } diff --git a/apps/web/src/components/ui/select.tsx b/apps/web/src/components/ui/select.tsx new file mode 100644 index 0000000..8333850 --- /dev/null +++ b/apps/web/src/components/ui/select.tsx @@ -0,0 +1,190 @@ +import * as React from "react" +import { Select as SelectPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react" + +function Select({ + ...props +}: React.ComponentProps) { + return +} + +function SelectGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectValue({ + ...props +}: React.ComponentProps) { + return +} + +function SelectTrigger({ + className, + size = "default", + children, + ...props +}: React.ComponentProps & { + size?: "sm" | "default" +}) { + return ( + + {children} + + + + + ) +} + +function SelectContent({ + className, + children, + position = "item-aligned", + align = "center", + ...props +}: React.ComponentProps) { + return ( + + + + + {children} + + + + + ) +} + +function SelectLabel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function SelectSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectScrollUpButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function SelectScrollDownButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +} diff --git a/apps/web/src/components/ui/textarea.tsx b/apps/web/src/components/ui/textarea.tsx new file mode 100644 index 0000000..04d27f7 --- /dev/null +++ b/apps/web/src/components/ui/textarea.tsx @@ -0,0 +1,18 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { + return ( +