From 140a4e1ff61f66047b883d103a0c09d84077bebe Mon Sep 17 00:00:00 2001 From: huyhoangnhh98 <54012965+huyhoangnhh98@users.noreply.github.com> Date: Tue, 12 May 2026 13:18:33 +0700 Subject: [PATCH] Improve responsive preview and design handoff outputs (#1224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: improve responsive design handoff * feat: refine cross-platform design outputs Changelog:\n- Add auto-fit responsive preview behavior for tablet/mobile frames.\n- Add landing page and OS widgets metadata options with project header chips.\n- Strengthen prompt contracts for modern breakpoints, app-specific modules, CJX-ready UX, and final product surfaces.\n- Require cross-platform outputs to use separate platform files instead of tabbed demo selectors.\n- Add DESIGN-MANIFEST.json plus richer handoff guidance to daemon/client exports.\n- Update archive/export tests for manifest and responsive viewport matrix. * feat: enforce screen-file design outputs Changelog:\n- Enforce screen-file-first generation for landing pages, app screens, platform surfaces, and OS widgets.\n- Update design handoff and manifest exports so coding tools map each screen file to separate routes/surfaces.\n- Strengthen minimal-brief visual guidance to avoid monochrome or unstyled design outputs. * fix: address responsive handoff review feedback * fix: address handoff review blockers * fix: preserve proxy auth and normalized export entry * fix: narrow frame wrapper filter to directory paths only * fix: make artifact save failure banner generic --------- Co-authored-by: Huy Hoàng --- CHANGELOG.md | 4 + apps/daemon/src/app-config.ts | 21 +- apps/daemon/src/projects.ts | 231 +++++++++++++++ apps/daemon/src/prompts/directions.ts | 58 ++-- apps/daemon/src/prompts/discovery.ts | 33 ++- apps/daemon/src/prompts/official-system.ts | 4 +- apps/daemon/src/prompts/system.ts | 52 ++++ apps/daemon/tests/app-config.test.ts | 8 +- apps/daemon/tests/project-archive.test.ts | 111 ++++++- apps/daemon/tests/prompts/system.test.ts | 13 + apps/web/src/components/FileViewer.tsx | 205 +++++++++++-- apps/web/src/components/NewProjectPanel.tsx | 219 +++++++++++++- apps/web/src/components/ProjectView.tsx | 116 +++++++- apps/web/src/components/SettingsDialog.tsx | 29 +- apps/web/src/i18n/locales/ar.ts | 11 + apps/web/src/i18n/locales/de.ts | 11 + apps/web/src/i18n/locales/en.ts | 24 +- apps/web/src/i18n/locales/es-ES.ts | 11 + apps/web/src/i18n/locales/fa.ts | 20 ++ apps/web/src/i18n/locales/fr.ts | 11 + apps/web/src/i18n/locales/hu.ts | 11 + apps/web/src/i18n/locales/id.ts | 20 ++ apps/web/src/i18n/locales/ja.ts | 11 + apps/web/src/i18n/locales/ko.ts | 11 + apps/web/src/i18n/locales/pl.ts | 11 + apps/web/src/i18n/locales/pt-BR.ts | 20 ++ apps/web/src/i18n/locales/ru.ts | 20 ++ apps/web/src/i18n/locales/tr.ts | 7 + apps/web/src/i18n/locales/uk.ts | 20 ++ apps/web/src/i18n/locales/zh-CN.ts | 20 ++ apps/web/src/i18n/locales/zh-TW.ts | 20 ++ apps/web/src/i18n/types.ts | 17 ++ apps/web/src/index.css | 227 ++++++++++++-- apps/web/src/runtime/exports.ts | 278 +++++++++++++++++- apps/web/src/state/config.ts | 16 +- apps/web/src/types.ts | 2 + apps/web/tests/components/FileViewer.test.tsx | 16 +- .../tests/components/NewProjectPanel.test.tsx | 3 + apps/web/tests/runtime/exports.test.ts | 114 +++++++ apps/web/tests/state/config.test.ts | 49 +++ packages/contracts/src/api/projects.ts | 15 + packages/contracts/src/prompts/directions.ts | 58 ++-- packages/contracts/src/prompts/discovery.ts | 34 ++- .../contracts/src/prompts/official-system.ts | 4 +- packages/contracts/src/prompts/system.ts | 48 +++ 45 files changed, 2068 insertions(+), 176 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c936f2f3d..816d75362 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Responsive design handoff improvements: tablet/mobile preview auto-fit, 2025–2026 responsive viewport matrix, landing page and OS widgets metadata chips, stricter cross-platform file output contracts, and DESIGN-HANDOFF/DESIGN-MANIFEST exports for coding-tool implementation. +- Screen-file-first design handoff policy: landing pages, app/product screens, platform surfaces, and OS widget surfaces are exported as distinct HTML files with matching handoff/manifest guidance instead of being merged into one long artifact. + ## [0.6.0] - 2026-05-09 A connectivity-and-iteration release: Open Design becomes a fully bidirectional MCP citizen (external MCP client with 39 templates), ships **Cloudflare Pages deployment** for generated artifacts (with custom domains), advances Critique Theater to **Phase 6** (interrupt + project-keyed run registry), and lands a redesigned top bar, draggable file tabs, batch delete, **vector PDF export**, **agent-callable research/search**, and **Orbit activity summaries**. Hyperframes learns the HTML-in-Canvas API. New BYOK provider (Ollama Cloud), new agent capabilities (Gemini 3 preview + GPT-5.1 codex picker + DeepSeek v4), new design systems (BMW M, Slack, Cisco, Webex, Mission Control, Urdu Modern), eight new skill bundles, and Turkish + Thai locales. 136 merged PRs since 0.5.0. diff --git a/apps/daemon/src/app-config.ts b/apps/daemon/src/app-config.ts index 40878158a..bd7c2be48 100644 --- a/apps/daemon/src/app-config.ts +++ b/apps/daemon/src/app-config.ts @@ -1,11 +1,16 @@ // Daemon-backed app preferences (onboarding state, agent/skill/DS selection). // -// The web frontend pushes non-sensitive preferences here via PUT -// /api/app-config; the daemon persists them to /app-config.json -// (where dataDir defaults to /.od but follows OD_DATA_DIR when -// set, keeping test and multi-namespace runs isolated). -// This survives browser storage resets and origin changes so onboarding -// and agent selection don't reappear unexpectedly. +// The web frontend pushes preferences here via PUT /api/app-config; the +// daemon persists them to /app-config.json (where dataDir defaults +// to /.od but follows OD_DATA_DIR when set, keeping test and +// multi-namespace runs isolated). This survives browser storage resets and +// origin changes so onboarding and agent selection don't reappear unexpectedly. +// +// `agentCliEnv` is intentionally limited by allowlist below. It may include +// proxy/auth overrides for local CLIs (for example ANTHROPIC_BASE_URL + +// ANTHROPIC_API_KEY for Claude Code, or OPENAI_BASE_URL + OPENAI_API_KEY for +// Codex). Those values are local-only and should not be logged or returned +// outside this machine. import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'; import { randomBytes } from 'node:crypto'; @@ -85,8 +90,8 @@ function validateTelemetry(raw: unknown): TelemetryPrefs | undefined { } const AGENT_CLI_ENV_KEYS: ReadonlyMap> = new Map([ - ['claude', new Set(['CLAUDE_CONFIG_DIR', 'CLAUDE_BIN'])], - ['codex', new Set(['CODEX_HOME', 'CODEX_BIN'])], + ['claude', new Set(['CLAUDE_CONFIG_DIR', 'CLAUDE_BIN', 'ANTHROPIC_BASE_URL', 'ANTHROPIC_API_KEY'])], + ['codex', new Set(['CODEX_HOME', 'CODEX_BIN', 'OPENAI_BASE_URL', 'OPENAI_API_KEY'])], ['copilot', new Set(['COPILOT_BIN'])], ['cursor-agent', new Set(['CURSOR_AGENT_BIN'])], ['deepseek', new Set(['DEEPSEEK_BIN'])], diff --git a/apps/daemon/src/projects.ts b/apps/daemon/src/projects.ts index 8f7cc249d..26c942753 100644 --- a/apps/daemon/src/projects.ts +++ b/apps/daemon/src/projects.ts @@ -24,6 +24,8 @@ import { const FORBIDDEN_SEGMENT = /^$|^\.\.?$/; const RESERVED_PROJECT_FILE_SEGMENTS = new Set(['.live-artifacts']); +const DESIGN_HANDOFF_FILENAME = 'DESIGN-HANDOFF.md'; +const DESIGN_MANIFEST_FILENAME = 'DESIGN-MANIFEST.json'; export const projectFileRenameTestHooks = { beforeCommit: null as null | ((paths: { source: string; target: string }) => Promise | void), }; @@ -191,6 +193,8 @@ export async function buildProjectArchive(projectsRoot, projectId, root, metadat binary: true, }); } + addDesignHandoff(zip, entries, archiveBaseName || path.basename(projectRoot)); + addDesignManifest(zip, entries, archiveBaseName || path.basename(projectRoot)); // Level 6 is the zlib default — balances speed and ratio for typical // project trees (HTML/CSS/JS plus a handful of assets). Level 9 buys // <5% on already-compressed PNGs/fonts at 2-3× CPU; level 1 produces @@ -343,6 +347,233 @@ async function collectArchiveEntries(dir, relDir, out) { } } +function addDesignHandoff(zip, entries, projectLabel) { + if (entries.some((entry) => entry.relPath === DESIGN_HANDOFF_FILENAME)) return; + zip.file(DESIGN_HANDOFF_FILENAME, buildDesignHandoff(entries, projectLabel), { + date: new Date(0), + binary: false, + }); +} + +function addDesignManifest(zip, entries, projectLabel) { + if (entries.some((entry) => entry.relPath === DESIGN_MANIFEST_FILENAME)) return; + zip.file(DESIGN_MANIFEST_FILENAME, buildDesignManifest(entries, projectLabel), { + date: new Date(0), + binary: false, + }); +} + +// A file is treated as a preview-chrome wrapper only when it lives inside +// a frames/ or device-frames/ directory, or its filename is an unambiguous +// wrapper template (browser-chrome.html, device-frame.html). Filenames +// like phone.html or iphone-upgrade.html are legitimate product-screen +// deliverables and must not be dropped from manifest screens. +const FRAME_WRAPPER_FILE_RE = /(^|\/)(frames?\/|device-frames?\/)|(^|\/)(browser-chrome|device-frame)\.html?$/i; + +function isFrameWrapperHtmlFile(file: string): boolean { + return FRAME_WRAPPER_FILE_RE.test(file); +} + +function projectFileMap(entries) { + const files = entries.map((entry) => entry.relPath).sort((a, b) => a.localeCompare(b)); + const htmlFiles = files.filter((name) => /\.html?$/i.test(name)); + const screenHtmlFiles = htmlFiles.filter((name) => !isFrameWrapperHtmlFile(name)); + const cssFiles = files.filter((name) => /\.css$/i.test(name)); + const jsFiles = files.filter((name) => /\.[cm]?[jt]sx?$/i.test(name)); + const assetFiles = files.filter((name) => !htmlFiles.includes(name) && !cssFiles.includes(name) && !jsFiles.includes(name)); + const entryFile = screenHtmlFiles.find((name) => /(^|\/)index\.html$/i.test(name)) + || screenHtmlFiles[0] + || htmlFiles.find((name) => /(^|\/)index\.html$/i.test(name)) + || htmlFiles[0] + || files[0] + || 'index.html'; + return { files, htmlFiles, screenHtmlFiles, cssFiles, jsFiles, assetFiles, entryFile }; +} + +function buildDesignManifest(entries, projectLabel) { + const { files, htmlFiles, screenHtmlFiles, cssFiles, jsFiles, assetFiles, entryFile } = projectFileMap(entries); + const screenFiles = screenHtmlFiles.length > 0 ? screenHtmlFiles : [entryFile]; + return JSON.stringify({ + schema: 'open-design.design-manifest.v1', + title: projectLabel || 'Open Design project', + entryFile, + sourceFiles: { + all: files, + html: htmlFiles, + css: cssFiles, + scriptsAndComponents: jsFiles, + assets: assetFiles, + }, + screens: screenFiles.map((file) => { + const isIndex = /(^|\/)index\.html?$/i.test(file); + const isLanding = /(^|\/)(landing|marketing)\.html?$/i.test(file) || /landing|marketing/i.test(file); + const isOsWidget = /widget|live-activity|lock-screen|home-screen/i.test(file); + const isApp = /app|dashboard|workspace|generator|translator|editor|screen/i.test(file); + return { + file, + role: isIndex && screenFiles.length > 1 ? 'launcher-overview' : isLanding ? 'landing-page' : isOsWidget ? 'os-widget-surface' : isApp ? 'product-screen' : 'screen', + implementationNote: isIndex && screenFiles.length > 1 + ? 'Use this as the navigation/overview entry only; implement each linked screen file as its own route/surface.' + : 'Preserve visual hierarchy, responsive behavior, and interactive states from this screen.', + }; + }), + screenFilePolicy: { + mode: 'screen-file-first', + entryFileRole: screenFiles.length > 1 && /(^|\/)index\.html?$/i.test(entryFile) ? 'launcher-overview' : 'primary-screen', + rules: [ + 'Each distinct user-facing screen or surface must be delivered and implemented as its own file/route.', + 'If a landing page is present or requested, keep it in landing.html and do not merge it into the product app screen.', + 'When multiple HTML screens exist, index.html is a launcher/overview only; it must not be treated as the combined final UI.', + 'Keep product app screens, landing pages, platform screens, and OS widget surfaces separate in production code.', + ], + }, + appModules: [ + 'Identify domain-specific in-app modules from the exported UI; do not reduce them to generic cards.', + 'For each major module, implement purpose, default/loading/empty/error/success states, and responsive behavior.', + 'Keep app modules separate from OS home-screen widgets in the production component model.', + ], + osWidgets: [ + 'If the export includes home-screen, lock-screen, Live Activity, tablet glance, or Android widget surfaces, implement them as platform quick-access surfaces outside the app UI.', + 'If none are present, do not invent OS widgets unless the product requirements request them.', + ], + landingPage: { + detection: 'Inspect files and screen names for a marketing/landing page surface. If present, keep it separate from product app screens.', + requiredSections: ['hero', 'value props', 'product proof/screenshots', 'feature proof', 'CTA'], + }, + tokens: { + source: cssFiles.length > 0 ? cssFiles : [entryFile], + required: ['background', 'surface', 'foreground', 'muted text', 'border', 'accent', 'radius', 'shadow', 'spacing', 'type scale', 'motion'], + note: 'Extract/freeze tokens before framework implementation so coding tools do not substitute default theme colors or typography.', + }, + interactions: { + source: jsFiles.length > 0 ? jsFiles : [entryFile], + requiredStates: ['default', 'hover', 'focus', 'active', 'disabled', 'loading', 'empty', 'error', 'success'], + requiredBehaviors: ['forms/validation where present', 'tabs/filters where present', 'dialogs/sheets/drawers where present', 'copy/generate/share actions where present', 'player or quick controls where present'], + note: 'If the prototype is static, derive missing behavior from visible controls and document it before coding.', + }, + responsiveViewports: [ + { name: 'mobile-compact', width: 360, height: 800, category: 'mobile', mustAvoidHorizontalScroll: true }, + { name: 'mobile-standard', width: 390, height: 844, category: 'mobile', mustAvoidHorizontalScroll: true }, + { name: 'mobile-large', width: 430, height: 932, category: 'mobile', mustAvoidHorizontalScroll: true }, + { name: 'foldable-small-tablet', width: 600, height: 960, category: 'foldable-tablet', mustAvoidHorizontalScroll: true }, + { name: 'tablet-portrait', width: 820, height: 1180, category: 'tablet', mustAvoidHorizontalScroll: true }, + { name: 'tablet-landscape', width: 1024, height: 768, category: 'tablet', mustAvoidHorizontalScroll: true }, + { name: 'laptop', width: 1366, height: 768, category: 'desktop', mustAvoidHorizontalScroll: true }, + { name: 'desktop', width: 1440, height: 900, category: 'desktop', mustAvoidHorizontalScroll: true }, + { name: 'wide', width: 1920, height: 1080, category: 'wide', mustAvoidHorizontalScroll: true }, + ], + implementationChecklist: [ + 'Open entryFile first and map screens, modules, tokens, and interactions.', + 'Extract tokens before writing framework components.', + 'Implement app-specific modules with real states instead of generic card grids.', + 'Preserve or rebuild JS interactions for meaningful UX actions.', + 'Validate screenshots at desktop/tablet/mobile viewports with no horizontal overflow.', + 'Keep landing pages, in-app modules, and OS widgets as separate implementation surfaces.', + ], + }, null, 2); +} + +function buildDesignHandoff(entries, projectLabel) { + const { files, htmlFiles, cssFiles, jsFiles, assetFiles, entryFile } = projectFileMap(entries); + const accentLikelyBrandLed = + files.some((name) => /(design|brand|tokens?|theme|style|tailwind|variables)\.(css|scss|sass|less|json|ts|tsx|js|jsx|md)$/i.test(name)) || + cssFiles.length > 0; + const hasResponsiveClues = + htmlFiles.length > 0 || + cssFiles.length > 0 || + files.some((name) => /(screens?|pages?|components?|app|src)\//i.test(name)); + const list = (items) => items.length > 0 ? items.map((name) => `- \`${name}\``).join('\n') : '- None detected'; + + return `# ${projectLabel || 'Open Design project'} implementation handoff + +This archive is the source of truth for turning the design into production code. Start from \`${entryFile}\`, then preserve the visual system, responsive behavior, and interactions found in the exported files. + +## Implementation target +- Build production UI from the exported design, not a loose reinterpretation. +- Preserve typography scale, spacing rhythm, color tokens, border radii, shadows, motion timing, and component states. +- Replace static placeholders only when the target app has real data or functional equivalents. +- Keep generated product UI free of Open Design chrome, preview labels, or design-process annotations. +- Treat this handoff as a visual contract: if implementation choices conflict, match the exported pixels and behavior first, then refactor internals. + +## Source map +- Primary entry: \`${entryFile}\` +- HTML screens detected: ${htmlFiles.length} +- Stylesheets detected: ${cssFiles.length} +- Script/component files detected: ${jsFiles.length} +- Supporting assets detected: ${assetFiles.length} + +## Responsive contract +Validate the implementation across this 2025–2026 viewport matrix: +- Mobile compact: 360×800 +- Mobile standard: 390×844 +- Mobile large: 430×932 +- Foldable / small tablet: 600×960 +- Tablet portrait: 820×1180 +- Tablet landscape: 1024×768 +- Laptop: 1366×768 +- Desktop: 1440×900 +- Wide desktop: 1920×1080 + +For responsive web exports, treat these as a modern breakpoint system for one adaptive web experience, not three fixed screenshots. Do not split responsive web into unrelated native app screens unless the project explicitly includes native targets. Use semantic layout thresholds, fluid \`clamp()\` type/spacing, and container queries where component width matters more than viewport width. ${hasResponsiveClues ? 'Preserve any CSS media queries, container queries, fluid \`clamp()\` scales, and layout changes already present in the exported files.' : 'If responsive rules are not present in the export, add them in the target implementation before shipping.'} + +## Design fidelity contract +- Extract reusable tokens before writing components: background, surface, foreground, muted text, border, accent, radius, shadow, spacing, type scale, and motion duration/easing. +- Map product screens, in-app modules/components, optional landing page, and optional OS widget surfaces before coding. Keep these surfaces separate in the target architecture. +- Match layout geometry: max-widths, gutters, grid columns, card proportions, sticky/fixed elements, and viewport-specific navigation. +- Preserve real copy, labels, and data shown in the export. Do not replace specific text with generic marketing filler. +- Preserve interactive affordances: hover, focus, pressed, disabled, loading, validation, copy/share, tab/accordion, modal/sheet, and keyboard states where present. +- Preserve accessibility semantics when converting: headings stay hierarchical, controls remain buttons/links/inputs, focus states stay visible. +- Do not keep prototype-only annotations, frame labels, or Open Design chrome in the production UI. + +## CJX-ready UX contract +- Use \`${DESIGN_MANIFEST_FILENAME}\` as the machine-readable map for screens, app modules, OS widgets, landing pages, tokens, interactions, and viewport checks. +- Screen-file-first: when multiple user-facing surfaces exist, implement each HTML screen as its own route/file. Treat \`index.html\` as a launcher/overview when the manifest marks it that way, not as a combined final UI. +- If \`landing.html\`, app screens, platform screens, or OS widget files exist, preserve those boundaries in the target app instead of merging them into one page. +- A single self-contained \`${entryFile}\` is acceptable only when the export truly contains one user-facing screen and its CSS/JS are structured enough to extract tokens, components, states, and behavior. +- If separate \`css/\` or \`js/\` files exist, treat them as source of truth for token/component/interactions before porting to React, Vue, SwiftUI, Compose, or another target stack. +- In-app modules/components are product UI blocks inside the app. OS widgets are home-screen/lock-screen/quick-access surfaces outside the app. Do not merge those concepts. + +## Color and brand contract +- Use the exported design tokens and product/domain context as the color source of truth. +- Do not introduce warm beige / cream / peach / pink / orange-brown background washes unless they are already explicit brand/reference colors in the export. +- ${accentLikelyBrandLed ? 'A stylesheet or design/token file was detected; inspect it for canonical color variables before choosing framework theme tokens.' : 'No obvious token stylesheet was detected; sample colors from the entry file and convert them into named tokens before coding.'} + +## Implementation sequence for AI coding tools +1. Open \`${entryFile}\` and \`${DESIGN_MANIFEST_FILENAME}\`; identify every screen file, launcher/overview file, app module, and interaction before coding. +2. If multiple HTML screens exist, map them to separate routes/surfaces first; do not merge \`landing.html\`, product app screens, platform screens, or OS widgets into one route. +3. Extract a token table from CSS/root styles and inline styles before building framework components. +4. Build product screens and domain-specific in-app modules from largest layout regions down to controls; avoid starting with isolated atoms that lose spatial intent. +5. Port responsive behavior across the modern viewport matrix and test each semantic breakpoint before cleanup. +6. Port interactions and states, then replace static placeholders only with real app data or functional equivalents. +7. Keep optional landing page and OS widget surfaces as separate surfaces if present. +8. Compare final screenshots against the export at 360×800, 390×844, 430×932, 820×1180, 1024×768, 1366×768, 1440×900, and 1920×1080 before declaring done. + +## Entry points +${list(htmlFiles)} + +## Styles +${list(cssFiles)} + +## Scripts/components +${list(jsFiles)} + +## Assets and supporting files +${list(assetFiles)} + +## Coding checklist for AI tools +1. Inspect \`${entryFile}\` and \`${DESIGN_MANIFEST_FILENAME}\` first and identify reusable components before coding. +2. Implement each user-facing screen file as its own route/surface; keep launcher, landing, app, platform, and OS widget files separate. +3. Extract design tokens into the target stack: colors, type scale, spacing, radius, shadows, and motion. +4. Implement layout with real 2025–2026 responsive breakpoints, fluid type/spacing, and container-query-aware component behavior; test with no horizontal overflow. +5. Preserve interactive controls, hover/focus/pressed states, form behavior, validation, and copy actions where present. +6. Implement domain-specific in-app modules with real states; do not flatten them into generic cards. +7. Keep landing page, product screens, and OS widget/quick-access surfaces separate when present. +8. Confirm the production result visually matches the exported design before refactoring internals. +9. Reject implementation shortcuts that flatten the design into generic cards, generic gradients, placeholder stats, or framework-default typography. +10. If a detail is ambiguous, keep the exported HTML/CSS/JS behavior rather than inventing a new pattern. +`; +} + export async function readProjectFile(projectsRoot, projectId, name, metadata?) { const dir = resolveProjectDir(projectsRoot, projectId, metadata); const file = await resolveSafeReal(dir, name); diff --git a/apps/daemon/src/prompts/directions.ts b/apps/daemon/src/prompts/directions.ts index b63383be0..5076b22e0 100644 --- a/apps/daemon/src/prompts/directions.ts +++ b/apps/daemon/src/prompts/directions.ts @@ -55,31 +55,31 @@ export const DESIGN_DIRECTIONS: DesignDirection[] = [ id: 'editorial-monocle', label: 'Editorial — Monocle / FT magazine', mood: - 'Print-magazine feel. Generous whitespace, large serif headlines, restrained palette of off-white paper + ink + a single warm accent. Confident, quietly intelligent.', + 'Print-magazine feel for explicitly editorial or publishing briefs. Generous whitespace, large serif headlines, restrained palette of neutral paper + ink + a single brand-justified accent. Do not use this as the default for commerce, SaaS, dashboards, or product utilities.', references: ['Monocle', 'The Financial Times Weekend', 'NYT Magazine', 'It\'s Nice That'], displayFont: "'Iowan Old Style', 'Charter', Georgia, serif", bodyFont: "-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif", palette: { - bg: 'oklch(97% 0.012 80)', // off-white paper - surface: 'oklch(99% 0.005 80)', - fg: 'oklch(20% 0.02 60)', // ink - muted: 'oklch(48% 0.015 60)', - border: 'oklch(89% 0.012 80)', - accent: 'oklch(58% 0.16 35)', // warm rust / clay + bg: 'oklch(98% 0.004 95)', // neutral paper, not beige wash + surface: 'oklch(100% 0.002 95)', + fg: 'oklch(20% 0.018 70)', // ink + muted: 'oklch(48% 0.012 70)', + border: 'oklch(90% 0.006 95)', + accent: 'oklch(52% 0.10 28)', // restrained editorial red; override from brand when available }, posture: [ 'serif display, sans body, mono for metadata only', 'no shadows, no rounded cards — borders + whitespace do the work', 'one decisive image, cropped only at the bottom', - 'kicker / eyebrow in mono uppercase, one accent color, used at most twice', + 'kicker / eyebrow in mono uppercase, one accent color, used at most twice; never create peach/pink/orange-beige page washes unless the brand/reference requires them', ], }, { id: 'modern-minimal', label: 'Modern minimal — Linear / Vercel', mood: - 'Quiet, precise, software-native. System fonts, near-greyscale palette, a single saturated accent. The chrome disappears so content is the only thing that registers.', + 'Quiet, precise, software-native. System fonts, crisp neutral foundations, and a small but visible product palette (primary + secondary + status/accent) so the interface feels shipped rather than greyscale. The chrome stays restrained while interaction states, illustrations, charts, and product moments carry color.', references: ['Linear', 'Vercel', 'Notion 2024', 'Stripe docs'], displayFont: "-apple-system, BlinkMacSystemFont, 'SF Pro Display', system-ui, sans-serif", @@ -97,34 +97,34 @@ export const DESIGN_DIRECTIONS: DesignDirection[] = [ 'tight letter-spacing on display sizes (-0.02em)', 'hairline borders only, no shadows except dropdowns/modals', 'mono numerics with `font-variant-numeric: tabular-nums`', - 'sticky frosted nav, content-led layouts (no hero illustrations)', - 'one accent: links + primary CTA, nothing else', + 'sticky frosted nav, content-led layouts with one product illustration, device mockup, or data visualization when it clarifies the product', + 'controlled color system: primary action color + one secondary signal + status colors; avoid monochrome/unstyled outputs, but never flood every card with gradients', ], }, { - id: 'warm-soft', - label: 'Warm & soft — Stripe pre-2020 / Headspace', + id: 'human-approachable', + label: 'Human / approachable — Airbnb / Duolingo systems', mood: - 'Cream backgrounds, soft accent, gentle radii. Reads like a thoughtful product magazine — friendly without being cute. Good for fintech, wellness, indie SaaS.', - references: ['Stripe pre-2020', 'Headspace', 'Substack', 'Mercury'], + 'Friendly and tactile without the generic cozy canvas. Uses a clean neutral background, product-led color system, generous radii, and clear hierarchy. Good for consumer tools, marketplaces, wellness, education, translation, AI assistants, and indie SaaS when the brand has not supplied a palette.', + references: ['Airbnb', 'Duolingo product surfaces', 'Miro', 'Mercury'], displayFont: - "'Tiempos Headline', 'Newsreader', 'Iowan Old Style', Georgia, serif", + "'Söhne', 'Avenir Next', -apple-system, BlinkMacSystemFont, system-ui, sans-serif", bodyFont: - "'Söhne', -apple-system, BlinkMacSystemFont, system-ui, sans-serif", + "-apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif", palette: { - bg: 'oklch(97% 0.018 70)', // warm cream - surface: 'oklch(99% 0.008 70)', - fg: 'oklch(22% 0.02 50)', - muted: 'oklch(50% 0.018 50)', - border: 'oklch(90% 0.014 70)', - accent: 'oklch(64% 0.13 28)', // terracotta + bg: 'oklch(98% 0.004 240)', + surface: 'oklch(100% 0 0)', + fg: 'oklch(20% 0.02 240)', + muted: 'oklch(50% 0.018 240)', + border: 'oklch(90% 0.006 240)', + accent: 'oklch(56% 0.12 170)', // brand-safe teal }, posture: [ - 'serif display, soft sans body', - 'gentle radii (12–16px), no hard 0px corners on content cards', - 'single accent used for primary CTA + one editorial flourish (a quote mark, a stat)', - 'soft inner glow on hero cards rather than drop shadows', - 'avoid icons; use real screenshots / photographs / illustrations', + 'sans display with strong weight contrast, system body for readability', + 'comfortable radii (12–18px) paired with crisp grid alignment', + 'primary action color plus a secondary/domain accent and clear status colors; use color to separate panels, states, and product moments', + 'subtle elevation only on interactive cards; tasteful gradients/glows are allowed for hero/device/product moments, never as a full-page beige/pastel wash', + 'avoid generic pastel/beige gradients; use real product screenshots, data, or labelled placeholders', ], }, { @@ -165,7 +165,7 @@ export const DESIGN_DIRECTIONS: DesignDirection[] = [ bodyFont: "ui-monospace, 'IBM Plex Mono', 'JetBrains Mono', Menlo, monospace", palette: { - bg: 'oklch(96% 0.004 100)', // off-white printer paper + bg: 'oklch(98% 0.004 240)', // neutral printer paper surface: 'oklch(100% 0 0)', fg: 'oklch(15% 0.02 100)', muted: 'oklch(40% 0.02 100)', diff --git a/apps/daemon/src/prompts/discovery.ts b/apps/daemon/src/prompts/discovery.ts index 045477e1e..120bf3b1b 100644 --- a/apps/daemon/src/prompts/discovery.ts +++ b/apps/daemon/src/prompts/discovery.ts @@ -42,12 +42,12 @@ When the user opens a new project or sends a fresh design brief, your **very fir "questions": [ { "id": "output", "label": "What are we making?", "type": "radio", "required": true, "options": ["Slide deck / pitch", "Single web prototype / landing", "Multi-screen app prototype", "Dashboard / tool UI", "Editorial / marketing page", "Other — I'll describe"] }, - { "id": "platform", "label": "Primary surface", "type": "radio", - "options": ["Mobile (iOS/Android)", "Desktop web", "Tablet", "Responsive — all sizes", "Fixed canvas (1920×1080)"] }, + { "id": "platform", "label": "Target platform", "type": "checkbox", "maxSelections": 4, + "options": ["Responsive web", "Desktop web", "iOS app", "Android app", "Tablet app", "Desktop app", "Fixed canvas (1920×1080)"] }, { "id": "audience", "label": "Who is this for?", "type": "text", "placeholder": "e.g. early-stage investors, dev-tools buyers, internal exec review" }, { "id": "tone", "label": "Visual tone", "type": "checkbox", "maxSelections": 2, - "options": ["Editorial / magazine", "Modern minimal", "Playful / illustrative", "Tech / utility", "Luxury / refined", "Brutalist / experimental", "Soft / warm"] }, + "options": ["Editorial / magazine", "Modern minimal", "Playful / illustrative", "Tech / utility", "Luxury / refined", "Brutalist / experimental", "Human / approachable"] }, { "id": "brand", "label": "Brand context", "type": "radio", "options": ["Pick a direction for me", "I have a brand spec — I'll share it", "Match a reference site / screenshot — I'll attach it"] }, { "id": "scale", "label": "Roughly how much?", "type": "text", @@ -64,7 +64,7 @@ Form authoring rules: - \`type\` is one of: \`radio\`, \`checkbox\`, \`select\`, \`text\`, \`textarea\`. - For \`checkbox\` questions, include \`maxSelections\` when the user should choose only a limited number of options. Do not encode limits only in the label text. - Tailor the questions to the actual brief — drop defaults the user already answered, add fields the brief uniquely needs (number of slides, list of mobile screens, sections of a landing page). -- **Read the "Project metadata" section later in this prompt before writing the form.** That block lists what the user already chose at create time (kind, fidelity, speakerNotes, animations, template). Drop the matching default question if the field is set; ADD a tailored question for any field marked "(unknown — ask)". For example, on a deck with \`speakerNotes: (unknown — ask…)\`, include a yes/no on speaker notes; on a template project where animations is unknown, include a motion radio. Don't re-ask the kind itself if metadata.kind is set — the user already told you. +- **Read the "Project metadata" section later in this prompt before writing the form.** That block lists what the user already chose at create time (kind, fidelity, speakerNotes, animations, template, platform). Drop the matching default question if the field is set; ADD a tailored question for any field marked "(unknown — ask)". For example, on a deck with \`speakerNotes: (unknown — ask…)\`, include a yes/no on speaker notes; on a template project where animations is unknown, include a motion radio; on a cross-platform project, ask which screens need native variants instead of re-asking platform. Don't re-ask the kind itself if metadata.kind is set — the user already told you. - Keep it under ~7 questions. Second batch in a follow-up form if needed. - Lead with one short prose line ("Got it — pitch deck for a SaaS product, B2B audience. Tell me the rest:") then the form. Do **not** write a long pre-amble. - After \`\`, **stop your turn**. Do not write code. Do not start tools. Do not narrate "I'll wait." @@ -113,7 +113,7 @@ Run brand-spec extraction *before* TodoWrite — five steps, each in its own \`B - Six color tokens (\`--bg\`, \`--surface\`, \`--fg\`, \`--muted\`, \`--border\`, \`--accent\`) in OKLch - Display + body + mono font stacks - 3–5 layout posture rules you observed (radii, border weight, accent budget) -5. **Vocalise.** State the system you'll use in one sentence ("warm cream background, single rust accent at oklch(58% 0.15 35), Newsreader display + system body") so the user can redirect cheaply. +5. **Vocalise.** State the system you'll use in one sentence ("deep navy product canvas, single electric-cyan accent at oklch(68% 0.16 220), geometric display + system body") so the user can redirect cheaply. Then proceed to RULE 3. @@ -140,7 +140,7 @@ The standard plan template (adapt the middle steps to the brief): - 2. (if branch B) Confirm brand-spec.md + bind to :root (if branch A) Bind chosen direction's palette to :root (else) Pick a direction matching the tone, bind to :root -- 3. Plan section/slide/screen list with rhythm (state list aloud before writing) +- 3. Plan section/slide/screen list with platform variants and rhythm (state list aloud before writing) - 4. Copy the seed template to project root - 5. Paste & fill the planned layouts/screens/slides - 6. Replace [REPLACE] placeholders with real, specific copy from the brief @@ -181,6 +181,7 @@ ${renderDirectionSpecBlock()} ### A. Embody the specialist Pick the persona before writing CSS: +- **Responsive / cross-platform prototype** → product systems designer. Define shared information architecture first, then explicit modern breakpoint variants: mobile compact (360px), mobile standard/large (390–430px), foldable/small tablet (600–744px), tablet portrait (768–834px), tablet landscape/large tablet (1024–1180px), laptop (1280–1366px), desktop (1440–1536px), and wide (1920px). Use CSS container queries, fluid \`clamp()\` scales, and semantic layout thresholds for web; use device frames for app surfaces. Never merely shrink desktop cards into a phone viewport. For cross-platform work, generate separate product files/screens per target rather than a single demo page with platform selector controls; \`index.html\` should only be an overview/launcher when multiple files exist. - **Slide deck** → slide designer. Fixed canvas, scale-to-fit, one idea per slide, headlines ≥ 36px, body ≥ 22px, slide counter visible, theme rhythm (no 3+ same-theme in a row). - **Mobile app prototype** → interaction designer. Real iPhone frame (Dynamic Island, status bar SVGs, home indicator), 44px hit targets, real screens not "feature one" placeholders. - **Landing / marketing** → brand designer. One hero, 3–6 sections, real copy, *one* decisive flourish. @@ -204,6 +205,8 @@ Every prototype / mobile / deck skill ships: - ❌ Filler copy — "Feature One / Feature Two", lorem ipsum - ❌ An icon next to every heading - ❌ A gradient on every background +- ❌ Warm beige / cream / peach / pink / orange-brown page backgrounds unless the user's brand, screenshots, or selected direction explicitly require them +- ❌ Product artifacts that expose designer settings, viewport selectors, platform toggles, target-count badges, "demo controls", or generated-design metadata as if they were app UI When you don't have a real value, leave a short honest placeholder (\`—\`, a grey block, a labelled stub) instead of inventing one. An honest placeholder beats a fake stat. @@ -214,13 +217,23 @@ Default to 2–3 differentiated directions on the same brief — different colou Show something visible early, even if it is a wireframe with grey blocks and labelled placeholders. The user redirects cheaply at this stage. Wrap the first pass in a visible artifact and *say* it is a wireframe. ### F. Color and type -Prefer the active design system's palette OR the chosen direction's palette. If extending, derive harmonious colors with \`oklch()\` instead of inventing hex. Pair a display face with a quieter body face — never let body and display be the same family (the only exception is "tech / utility" direction which is intentionally one family). One accent colour, used at most twice per screen. +Prefer the active design system's palette OR the chosen direction's palette. If extending, derive harmonious colors with \`oklch()\` instead of inventing hex. The background must be selected from the user's product domain, brand assets, screenshots, or chosen direction — never from generic app chrome or a default cozy canvas. For product utilities, marketplaces, dashboards, and SaaS, start from neutral or brand-colored foundations; do not fall back to warm beige / peach / pink / orange-brown Claude-style canvases just because no brand was provided. Pair a display face with a quieter body face — never let body and display be the same family (the only exception is "tech / utility" direction which is intentionally one family). One accent colour, used at most twice per screen. ### G. Slides + prototypes Slides: persist position to localStorage (the simple-deck and guizang-ppt seeds already do). Tag slides with \`data-screen-label="01 Title"\`. Slide numbers are 1-indexed. Theme rhythm: no 3+ same-theme in a row. -Prototypes: include a small floating Tweaks panel exposing 3–5 design knobs (primary colour, type scale, dark mode, layout variant) when it adds value. +Product prototypes: do **not** include floating Tweaks panels, platform/settings choosers, theme knobs, viewport toggles, or other designer/demo controls in the artifact. If variation controls are useful for internal iteration, keep them out of final product files unless the user explicitly asks for a design-system/spec dashboard. -### H. Multi-device + multi-screen layouts — use shared frames +### H. Cross-platform + multi-device layouts — use platform contracts and shared frames +When the user selects multiple platform targets or metadata says \`platform: responsive\`, design the same product across surfaces instead of one web-only page. Apply these contracts: + +- **Responsive web**: include desktop, tablet, and mobile states for the same web product. Use semantic layout regions, fluid type with \`clamp()\`, breakpoint/container-query adaptations, and verify no horizontal scroll at 360px / 390px / 430px / 600px / 820px / 1024px / 1366px / 1440px / 1920px. The mobile layout must be redesigned for small screens with usable spacing, prioritised content, and real product navigation — not a squeezed desktop or tiny centered poster. +- **iOS app**: create a dedicated iOS product file/screen (for example \`mobile-ios.html\`) with an iPhone frame, Dynamic Island/status/home indicators, 44px minimum hit targets, iOS-safe bottom navigation or sheet patterns, and no Android-only Material navigation. +- **Android app**: create a dedicated Android product file/screen (for example \`mobile-android.html\`) with a Pixel frame, status bar + nav bar, 48dp hit targets, Material navigation patterns, and no iOS-only chrome. +- **Tablet**: create a dedicated tablet product file/screen (for example \`tablet.html\`) with split panes, sidebars, inspectors, and larger touch targets; do not simply scale the phone UI up or let tablet layouts overflow horizontally. +- **Desktop app**: include desktop chrome/sidebar density, keyboard-friendly states, resizable panes, and hover/focus states. +- **App-specific modules/components**: every product/app prototype must include domain-specific in-app modules by default (not optional): player controls for media, streak/check-in modules for habits, cart/order/coupon modules for commerce, balance/transaction/budget modules for finance, etc. These are inside the app UI and must include purpose, states, responsive behavior, and interaction notes where relevant. +- **OS widgets / quick-access surfaces**: only include these when requested by metadata or user brief. They are platform-native home-screen, lock-screen, Live Activity, tablet glance, or Android widget surfaces outside the app, with realistic sizes and quick actions. +- **CJX-ready UX**: artifacts must be implementation-ready. Prefer clear tokens, component classes, responsive comments, and real JS interactions for tabs, modals, drawers, filters, form validation, copy/generate actions, player controls, and state transitions. A self-contained \`index.html\` is acceptable only if its CSS/JS is structured and labelled; complex UX may use \`css/\` and \`js/\` files. When the brief calls for showing the SAME product across multiple devices (desktop + tablet + phone) or showing MULTIPLE screens of the same app side-by-side (onboarding 1 → 2 → 3, or feed → detail → checkout), do NOT re-draw a phone/laptop frame from scratch. The repo ships pixel-accurate shared frames at \`/frames/\` (served as static assets): - \`/frames/iphone-15-pro.html\` — 390 × 844, Dynamic Island @@ -251,7 +264,7 @@ Then in \`index.html\` use: width="390" height="844" loading="lazy"> \`\`\` -The single-screen \`mobile-app\` skill already inlines the iPhone frame in its seed; you only need the shared frames for the multi-device / multi-screen case. Don't re-draw — use these. +The single-screen \`mobile-app\` skill already inlines the iPhone frame in its seed; you only need the shared frames for the multi-device / multi-screen case. Don't re-draw — use these. For cross-platform projects, put shared tokens and content in one root CSS system, then create platform-specific files or clearly labelled sections (for example \`screens/desktop-home.html\`, \`screens/ios-home.html\`, \`screens/android-home.html\`) so reviewers can compare native adaptations side by side. ### I. Restraint over ornament "One thousand no's for every yes." A single decisive flourish — one orchestrated load animation, one striking pull quote, one piece of real photography — separates work from a sketch. Three competing flourishes turn it back into noise. diff --git a/apps/daemon/src/prompts/official-system.ts b/apps/daemon/src/prompts/official-system.ts index 88bc21aca..16c11c4d7 100644 --- a/apps/daemon/src/prompts/official-system.ts +++ b/apps/daemon/src/prompts/official-system.ts @@ -60,7 +60,7 @@ PDFs, PPTX, DOCX: you can extract them via Bash (\`unzip\`, \`pdftotext\`, etc.) - Keep individual files under ~1000 lines. If you're approaching that, split into smaller JSX/CSS files and \`