From 99be636b9ea45fa63c8be87f1fcbcb8dcdafb1d4 Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+catomean@users.noreply.github.com> Date: Mon, 31 Aug 2026 10:16:51 +0200 Subject: [PATCH 1/3] chore: add a formatter, matching the style this repo already writes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit singleQuote=true was chosen by counting this repo's own imports, not by fleet decree. The fleet is genuinely split and the two repos that already had a .prettierrc disagreed with each other, so there was no standard to restore. Quote style does not cross repo boundaries; having a gate does. Markdown is ignored for now — prettier rewraps prose, which would bury the real diff. Co-Authored-By: Claude Opus 5 --- .prettierignore | 31 +++++++++++++++++++++++++++++++ .prettierrc | 9 +++++++++ package-lock.json | 19 ++++++++++++++++++- package.json | 7 +++++-- 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..b698663 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,31 @@ +# Build output and vendored trees — formatting these is noise. +node_modules +.next +dist +build +out +coverage +.turbo +.vercel +*.min.js +*.min.css + +# Generated during a build, so it is absent locally and present in CI — which +# makes a clean local --check no evidence at all. Contentlayer's output also +# uses import assertions, which prettier's parser rejects outright. +.contentlayer +.astro +.svelte-kit +storybook-static +test-results +playwright-report + +# Lockfiles are generated; prettier would rewrite them wholesale. +package-lock.json +pnpm-lock.yaml +yarn.lock + +# Markdown is deliberately out of scope for now. Prettier rewraps prose, which +# is where it is most opinionated and least useful, and it would bury the real +# diff. Remove this line when you want docs formatted too. +*.md diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..616247a --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": true, + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "trailingComma": "all", + "arrowParens": "always", + "endOfLine": "lf" +} diff --git a/package-lock.json b/package-lock.json index 9df2ca9..df64f18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,11 +15,12 @@ "eslint": "^9.39.5", "eslint-plugin-react-hooks": "^5.2.0", "globals": "^15.15.0", + "prettier": "3.9.6", "typescript": "^5.8.2", "typescript-eslint": "^8.67.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "peerDependencies": { "react": ">=18" @@ -1348,6 +1349,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 417d5de..5072810 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,10 @@ "lint": "eslint .", "typecheck": "tsc -p tsconfig.json --noEmit", "test": "node --test test/*.test.js", - "verify": "npm run lint && npm run typecheck && npm run build && npm test", - "prepare": "npm run build" + "verify": "npm run format:check && npm run lint && npm run typecheck && npm run build && npm test", + "prepare": "npm run build", + "format": "prettier --write .", + "format:check": "prettier --check ." }, "peerDependencies": { "react": ">=18" @@ -71,6 +73,7 @@ "eslint": "^9.39.5", "eslint-plugin-react-hooks": "^5.2.0", "globals": "^15.15.0", + "prettier": "3.9.6", "typescript": "^5.8.2", "typescript-eslint": "^8.67.0" } From f9d0169a66cb9e4a17aed150087aebaf17e9be61 Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+catomean@users.noreply.github.com> Date: Mon, 31 Aug 2026 10:17:05 +0200 Subject: [PATCH 2/3] style: format with prettier (15 files) Mechanical. No behaviour change. This SHA is listed in .git-blame-ignore-revs so `git blame` skips it. Co-Authored-By: Claude Opus 5 --- .github/dependabot.yml | 4 +- eslint.config.mjs | 10 +- examples/next/app/globals.css | 223 +++++++++++++++++++++++++++------- examples/next/app/page.tsx | 109 +++++++++++++---- examples/next/lib/complete.ts | 77 +++++++++--- examples/next/lib/form.ts | 44 +++++-- src/assist.ts | 4 +- src/fields.ts | 14 ++- src/merge.ts | 10 +- src/prompt.ts | 16 ++- src/react.ts | 25 ++-- src/sanitize.ts | 26 ++-- src/server.ts | 4 +- test/core.test.js | 43 ++++--- test/package.test.js | 20 +-- 15 files changed, 462 insertions(+), 167 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a646416..554fa37 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,7 +11,7 @@ version: 2 updates: - package-ecosystem: npm - directory: "/" + directory: '/' schedule: interval: weekly open-pull-requests-limit: 5 @@ -20,7 +20,7 @@ updates: update-types: [minor, patch] - package-ecosystem: github-actions - directory: "/" + directory: '/' schedule: interval: weekly open-pull-requests-limit: 3 diff --git a/eslint.config.mjs b/eslint.config.mjs index c79c0bd..d521eba 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -2,10 +2,10 @@ // package is ~1k lines of library code, and a bespoke rule set would be a // second opinion to maintain for no benefit. The floor is "lint runs and can // fail", not "lint encodes taste". -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import tseslint from 'typescript-eslint' +import js from '@eslint/js'; +import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; +import tseslint from 'typescript-eslint'; export default tseslint.config( { @@ -47,4 +47,4 @@ export default tseslint.config( files: ['test/**/*.js', 'scripts/**/*.{js,mjs}'], languageOptions: { globals: { ...globals.node, ...globals.nodeBuiltin } }, }, -) +); diff --git a/examples/next/app/globals.css b/examples/next/app/globals.css index ee9f3ea..7facee2 100644 --- a/examples/next/app/globals.css +++ b/examples/next/app/globals.css @@ -13,8 +13,8 @@ --radius-card: 14px; --radius-control: 8px; --shadow-card: 0 1px 3px rgb(0 0 0 / 0.07); - --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; - --font-mono: ui-monospace, SFMono-Regular, "JetBrains Mono", monospace; + --font-sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif; + --font-mono: ui-monospace, SFMono-Regular, 'JetBrains Mono', monospace; } @media (prefers-color-scheme: dark) { @@ -30,7 +30,9 @@ } } -* { box-sizing: border-box; } +* { + box-sizing: border-box; +} body { margin: 0; background: var(--color-bg); @@ -38,46 +40,181 @@ body { font-family: var(--font-sans); line-height: 1.5; } -.wrap { max-width: 60rem; margin: 0 auto; padding: 2rem 1rem 4rem; } -h1 { margin: 0 0 .25rem; font-size: 1.9rem; letter-spacing: -0.02em; } -.lede { margin: 0 0 .75rem; color: var(--color-text-muted); max-width: 44rem; } -.engine { font-size: .85rem; padding: .55rem .75rem; border-radius: var(--radius-control); border: 1px solid var(--color-border); } -.engine-live { color: var(--color-brand); } -.engine-offline { color: var(--color-text-muted); } +.wrap { + max-width: 60rem; + margin: 0 auto; + padding: 2rem 1rem 4rem; +} +h1 { + margin: 0 0 0.25rem; + font-size: 1.9rem; + letter-spacing: -0.02em; +} +.lede { + margin: 0 0 0.75rem; + color: var(--color-text-muted); + max-width: 44rem; +} +.engine { + font-size: 0.85rem; + padding: 0.55rem 0.75rem; + border-radius: var(--radius-control); + border: 1px solid var(--color-border); +} +.engine-live { + color: var(--color-brand); +} +.engine-offline { + color: var(--color-text-muted); +} -.bar { margin: 1.5rem 0; padding: 1rem; background: var(--color-surface); - border: 1px solid var(--color-border); border-radius: var(--radius-card); box-shadow: var(--shadow-card); } -.bar textarea { width: 100%; font: inherit; padding: .6rem; resize: vertical; - border: 1px solid var(--color-border); border-radius: var(--radius-control); - background: var(--color-bg); color: var(--color-text); } -.bar-actions { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-top: .6rem; } -.intent { font-size: .8rem; color: var(--color-text-muted); margin-left: auto; } -button { font: inherit; cursor: pointer; padding: .5rem .9rem; border-radius: var(--radius-control); - border: 1px solid transparent; background: var(--color-action); color: #fff; min-height: 44px; } -button:disabled { opacity: .5; cursor: not-allowed; } -button.ghost { background: transparent; color: var(--color-text); border-color: var(--color-border); } -.chips { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .7rem; } -.chip { background: transparent; border: 1px dashed var(--color-border); color: var(--color-text-muted); - font-size: .82rem; text-align: left; min-height: 0; padding: .35rem .6rem; } -.error { color: var(--color-danger); margin: .6rem 0 0; font-size: .9rem; } +.bar { + margin: 1.5rem 0; + padding: 1rem; + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-card); + box-shadow: var(--shadow-card); +} +.bar textarea { + width: 100%; + font: inherit; + padding: 0.6rem; + resize: vertical; + border: 1px solid var(--color-border); + border-radius: var(--radius-control); + background: var(--color-bg); + color: var(--color-text); +} +.bar-actions { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; + margin-top: 0.6rem; +} +.intent { + font-size: 0.8rem; + color: var(--color-text-muted); + margin-left: auto; +} +button { + font: inherit; + cursor: pointer; + padding: 0.5rem 0.9rem; + border-radius: var(--radius-control); + border: 1px solid transparent; + background: var(--color-action); + color: #fff; + min-height: 44px; +} +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} +button.ghost { + background: transparent; + color: var(--color-text); + border-color: var(--color-border); +} +.chips { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + margin-top: 0.7rem; +} +.chip { + background: transparent; + border: 1px dashed var(--color-border); + color: var(--color-text-muted); + font-size: 0.82rem; + text-align: left; + min-height: 0; + padding: 0.35rem 0.6rem; +} +.error { + color: var(--color-danger); + margin: 0.6rem 0 0; + font-size: 0.9rem; +} -.grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); } -.field { display: flex; flex-direction: column; gap: .3rem; padding: .75rem; - border: 1px solid var(--color-border); border-radius: var(--radius-card); background: var(--color-surface); } -.field.just-changed { background: var(--color-changed); border-color: var(--color-action); } -.label { font-size: .85rem; font-weight: 600; display: flex; align-items: center; gap: .4rem; } -.tag { font-style: normal; font-size: .65rem; text-transform: uppercase; letter-spacing: .06em; - color: var(--color-action); border: 1px solid currentColor; border-radius: 999px; padding: 0 .35rem; } -.field input, .field select, .field textarea { font: inherit; padding: .45rem; - border: 1px solid var(--color-border); border-radius: var(--radius-control); - background: var(--color-bg); color: var(--color-text); width: 100%; } -.field input[type='checkbox'] { width: auto; min-height: 24px; } -.field small { color: var(--color-text-muted); font-size: .75rem; } +.grid { + display: grid; + gap: 1rem; + grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); +} +.field { + display: flex; + flex-direction: column; + gap: 0.3rem; + padding: 0.75rem; + border: 1px solid var(--color-border); + border-radius: var(--radius-card); + background: var(--color-surface); +} +.field.just-changed { + background: var(--color-changed); + border-color: var(--color-action); +} +.label { + font-size: 0.85rem; + font-weight: 600; + display: flex; + align-items: center; + gap: 0.4rem; +} +.tag { + font-style: normal; + font-size: 0.65rem; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--color-action); + border: 1px solid currentColor; + border-radius: 999px; + padding: 0 0.35rem; +} +.field input, +.field select, +.field textarea { + font: inherit; + padding: 0.45rem; + border: 1px solid var(--color-border); + border-radius: var(--radius-control); + background: var(--color-bg); + color: var(--color-text); + width: 100%; +} +.field input[type='checkbox'] { + width: auto; + min-height: 24px; +} +.field small { + color: var(--color-text-muted); + font-size: 0.75rem; +} -.transcript { margin-top: 1.5rem; } -.transcript p { margin: .3rem 0; font-size: .9rem; } -.transcript .assistant { color: var(--color-text-muted); } -.values { margin-top: 1.5rem; } -.values pre { overflow-x: auto; background: var(--color-surface); padding: .8rem; - border: 1px solid var(--color-border); border-radius: var(--radius-card); font-family: var(--font-mono); font-size: .8rem; } -code { font-family: var(--font-mono); } +.transcript { + margin-top: 1.5rem; +} +.transcript p { + margin: 0.3rem 0; + font-size: 0.9rem; +} +.transcript .assistant { + color: var(--color-text-muted); +} +.values { + margin-top: 1.5rem; +} +.values pre { + overflow-x: auto; + background: var(--color-surface); + padding: 0.8rem; + border: 1px solid var(--color-border); + border-radius: var(--radius-card); + font-family: var(--font-mono); + font-size: 0.8rem; +} +code { + font-family: var(--font-mono); +} diff --git a/examples/next/app/page.tsx b/examples/next/app/page.tsx index e9559fc..ecb6638 100644 --- a/examples/next/app/page.tsx +++ b/examples/next/app/page.tsx @@ -21,7 +21,10 @@ export default function Page() { const [engine, setEngine] = useState<{ mode: string; model: string } | null>(null); useEffect(() => { - fetch('/api/mode').then(r => r.json()).then(setEngine).catch(() => setEngine(null)); + fetch('/api/mode') + .then((r) => r.json()) + .then(setEngine) + .catch(() => setEngine(null)); }, []); async function send(text: string) { @@ -36,8 +39,8 @@ export default function Page() {

ai-forms

- Describe the job. Then keep talking to it. The second sentence patches the - form — it does not wipe it and start over. + Describe the job. Then keep talking to it. The second sentence patches the form — + it does not wipe it and start over.

{engine && (

@@ -53,8 +56,8 @@ export default function Page() { rows={2} value={draft} placeholder={form.isEmpty ? 'Describe the role…' : 'Now change something…'} - onChange={e => setDraft(e.target.value)} - onKeyDown={e => { + onChange={(e) => setDraft(e.target.value)} + onKeyDown={(e) => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) void send(draft); }} /> @@ -62,15 +65,24 @@ export default function Page() { - - + + intent: {form.isEmpty ? 'fill' : 'refine'} (inferred)

- {(form.isEmpty ? [FILL_EXAMPLE] : REFINE_EXAMPLES).map(example => ( - ))} @@ -79,11 +91,18 @@ export default function Page() {
- {FIELDS.filter(f => !f.aiExcluded).map(field => ( -