Replace the UI with the Organic design handoff (docs/design_handoff_pansy_ui)
The frontend is rebuilt screen by screen from the handoff: warm cream ground, terracotta + sage accents, Caprasimo over Figtree, every control a pill. Same React/Vite/TanStack stack and the same lib/ data layer; the presentation is new. - Tokens: web/src/styles/index.css declares the handoff's styles.css variables through Tailwind's @theme under the same names; dark mode is those variables overridden on <html> by the handoff's pansy-theme.js, inlined in index.html so it runs before first paint. Lucide glyphs at stroke 2.75; a small pill kit (Button, Dialog, Field, Seg, Toggle, Tag, toast). - Login / Register: the centered column over soft accent circles; OIDC button and signup footer still follow /auth/providers. - Gardens: cards with a real SVG plot thumbnail (objects + plant-colored dots from /full), a `plan` tag for "<name> — <year>" copies, shares line, Open + share/copy/edit/delete; New garden / Share / Plan-a-season dialogs. - Plants: monogram markers derived from the name (collision-resolved across the catalog — replaces emoji icons), category chips, expandable lot cards, the scan-packet flow as a two-step dialog that never auto-creates. - Settings: Appearance (theme seg), Who gets in (read-only sign-in config), Garden assistant (self-saving toggle + chat/vision model fields), You. - Editor: a new canvas with the prototype's pointer model (wheel-to-cursor, pinch about the centroid, 3″ snap, one PATCH per drop, semantic-zoom monograms/labels), plus corner resize handles; desktop three-card workspace (toolkit | plan | rail with Plot/Journal/History/Assistant) and, below 760px of container width, the phone chrome (header, peek panel, tool strip, mode bar). Seasons as a segmented control over the years with data plus plan copies; Undo re-reads history before reverting the newest step. - Public read-only view and the register page restyled to match. - GET /settings gains a read-only `auth` view (registration mode, local auth, OIDC issuer) so the Settings page can show what's in force. - README / DESIGN.md / CLAUDE.md updated; @use-gesture/react dropped. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
// Season plans are whole-garden copies (POST /gardens/:id/copy): a separate
|
||||
// garden you rearrange freely while this year's stays put. The API keeps no link
|
||||
// between a copy and its source, so the relationship rides on the name the copy
|
||||
// is given by default — "Home Garden — 2027" — which is what the editor's season
|
||||
// control and the gardens list read back. Rename the copy and it is simply a
|
||||
// garden again; nothing breaks, it just stops being offered as a plan.
|
||||
|
||||
const PLAN_RE = /^(.+?)\s+[—–-]\s+(\d{4})$/
|
||||
|
||||
export interface PlanName {
|
||||
base: string
|
||||
year: number
|
||||
}
|
||||
|
||||
/** "Home Garden — 2027" → { base: "Home Garden", year: 2027 }, else null. */
|
||||
export function parsePlanName(name: string): PlanName | null {
|
||||
const m = name.trim().match(PLAN_RE)
|
||||
if (!m) return null
|
||||
return { base: m[1].trim(), year: Number(m[2]) }
|
||||
}
|
||||
|
||||
/** The default name for a plan copy of `base` for `year`. */
|
||||
export function planNameFor(base: string, year: number): string {
|
||||
return `${base} — ${year}`
|
||||
}
|
||||
|
||||
/** Whether a garden reads as a forward-looking plan (a year-suffixed copy for
|
||||
* this year or later). Past years are archives, not plans, and get no tag. */
|
||||
export function planYearOf(name: string, now = new Date()): number | null {
|
||||
const p = parsePlanName(name)
|
||||
return p && p.year >= now.getFullYear() ? p.year : null
|
||||
}
|
||||
|
||||
/** The plan copies of `base` among `gardens`, ascending by year. */
|
||||
export function planGardensOf<G extends { id: number; name: string }>(base: string, gardens: readonly G[]): (G & { year: number })[] {
|
||||
const out: (G & { year: number })[] = []
|
||||
for (const g of gardens) {
|
||||
const p = parsePlanName(g.name)
|
||||
if (p && p.base === base.trim()) out.push({ ...g, year: p.year })
|
||||
}
|
||||
return out.sort((a, b) => a.year - b.year)
|
||||
}
|
||||
Reference in New Issue
Block a user