#!/usr/bin/env node 'use strict'; const fs = require('node:fs'); const path = require('node:path'); const readline = require('node:readline'); const VALID_TYPES = ['standard', 'landing', 'form', 'list', 'detail']; function parseArgs() { const args = process.argv.slice(2); const result = {}; for (let i = 0; i < args.length; i++) { if (args[i] === '--page' && args[i + 1]) result.page = args[++i]; if (args[i] === '--scenario' && args[i + 1]) result.scenario = args[++i]; if (args[i] === '--type' && args[i + 1]) result.type = args[++i]; } return result; } function prompt(rl, question) { return new Promise((resolve) => rl.question(question, resolve)); } async function getInputs(args) { let { page, scenario, type } = args; if (page && scenario && type) return { page, scenario, type }; const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); if (!page) { page = await prompt(rl, 'Page name (e.g. Homepage): '); page = page.trim(); } if (!scenario) { scenario = await prompt(rl, 'Scenario (e.g. "Harriet finds a service"): '); scenario = scenario.trim(); } if (!type) { console.log(`Types: ${VALID_TYPES.join(' | ')}`); type = await prompt(rl, 'Page type: '); type = type.trim(); } rl.close(); return { page, scenario, type }; } function toKebab(str) { return str .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); } function todayISO() { return new Date().toISOString().slice(0, 10); } function generatePageSpec(page, scenario, type) { const kebab = toKebab(page); const date = todayISO(); const typeNotes = { standard: 'Standard content page', landing: 'Landing / marketing page — conversion-focused', form: 'Form page — data entry and submission', list: 'List / index page — browse and filter content', detail: 'Detail page — deep view of a single item', }; return ` ### [page-number]-${kebab} **Previous Step:** ← [Previous page](./previous-page.md) **Next Step:** → [Next page](./next-page.md) ![${page}](Sketches/[page-number]-${kebab}.jpg) **Previous Step:** ← [Previous page](./previous-page.md) **Next Step:** → [Next page](./next-page.md) --- # [page-number]-${kebab} > **Type:** ${typeNotes[type] || type} > **Created:** ${date} ## Page Metadata | Property | Value | |----------|-------| | **Scenario** | ${scenario} | | **Page Number** | [Assign a number] | | **Platform** | [Mobile web / Desktop / PWA / Native] | | **Page Type** | [Full Page / Modal / Drawer / Popup] | | **Viewport** | [Mobile-first / Desktop-first] | | **Interaction** | [Touch-first / Mouse+keyboard] | | **Visibility** | [Public / Authenticated / Admin] | --- ## Overview **Page Purpose:** [Describe the purpose of this page and the job it must accomplish for the user] **User Situation:** [What brings the user here? What context are they in?] **Success Criteria:** [How will we know this page succeeded? What does the user achieve?] **Entry Points:** - [How users arrive at this page] **Exit Points:** - [Where users go next after this page] --- ## Reference Materials **Strategic Foundation:** - [Product Brief](../../A-Product-Brief/01-product-brief.md) - [Brief description of relevance] - [Trigger Map](../../B-Trigger-Map/) - [Brief description of relevance] **Related Pages:** - [Related page](./related-page.md) **Design System:** - [Component](../../E-Design-System/00-design-system.md) --- ## Layout Structure [High-level description of page layout and how sections relate to each other] \`\`\` +------------------+ | Header | +------------------+ | Main Content | | | +------------------+ | Footer | +------------------+ \`\`\` --- ## Spacing **Scale:** [Spacing Scale](../../E-Design-System/00-design-system.md#spacing-scale) | Property | Token | |----------|-------| | Page padding (horizontal) | [e.g., space-md mobile / space-lg desktop] | | Section gap | [e.g., space-xl] | | Element gap (default within sections) | [e.g., space-md] | | Component gap (within groups) | [e.g., space-sm] | --- ## Typography **Scale:** [Type Scale](../../E-Design-System/00-design-system.md#type-scale) | Element | Semantic | Size | Weight | Typeface | |---------|----------|------|--------|----------| | [Page title] | H1 | [e.g., text-2xl] | [e.g., bold] | [e.g., display / default] | | [Section heading] | H2 | [e.g., text-xl] | [e.g., semibold] | [default] | | [Body text] | p | text-md | normal | [default] | | [Caption/helper] | p | [e.g., text-xs] | normal | [default] | --- ## Page Sections ### Section: [Section Name] **OBJECT ID:** \`${kebab}-[section-name]\` | Property | Value | |----------|-------| | Purpose | [What this section does and why it exists on this page] | | Component | [[Design System Component]](../../E-Design-System/00-design-system.md) | | Padding | [e.g., space-lg space-md] | | Element gap | [e.g., space-md — or "default" if same as page-level] | --- #### [Object Name] **OBJECT ID:** \`${kebab}-[object-name]\` | Property | Value | |----------|-------| | Component | [[Component]](../../E-Design-System/00-design-system.md) | | Translation Key | \`[translation.key]\` | | SE | "[Swedish text]" | | EN | "[English text]" | | Behavior | [onClick / onChange / etc.] | --- #### [Object Name 2] **OBJECT ID:** \`${kebab}-[object-name-2]\` | Property | Value | |----------|-------| | Component | [[Component]](../../E-Design-System/00-design-system.md) | | Translation Key | \`[translation.key]\` | | SE | "[Swedish text]" | | EN | "[English text]" | --- ## Page States | State | When | Appearance | Actions | |-------|------|------------|---------| | Default | [Normal condition] | [Description of default appearance] | [Available actions] | | Loading | [When data is fetching] | [Skeleton / spinner description] | [Available actions] | | Empty | [No content to display] | [Empty state description] | [Available actions] | | Error | [When something fails] | [Error message description] | [Recovery actions] | | Success | [After successful action] | [Confirmation description] | [Next steps] | --- ## Conditional Sections Include these micro-instructions when applicable: | Condition | Include | |-----------|---------| | Public page (SEO needed) | → [meta-content.instructions.md](../instructions/meta-content.instructions.md) | | Public page (SEO needed) | → [seo-content.instructions.md](../instructions/seo-content.instructions.md) | | Has forms/inputs | → [form-validation.instructions.md](../instructions/form-validation.instructions.md) | | Needs API data | → [data-api.instructions.md](../instructions/data-api.instructions.md) | | Multiple breakpoints | → [responsive.instructions.md](../instructions/responsive.instructions.md) | | Final review | → [accessibility.instructions.md](../instructions/accessibility.instructions.md) | | **Always (spec creation/audit)** | → [open-questions.instructions.md](../instructions/open-questions.instructions.md) | --- ## Technical Notes [Any constraints, performance requirements, or implementation notes relevant to this page] --- ## Open Questions _No open questions at this time._ --- ## Checklist - [ ] Page purpose clear - [ ] All Object IDs assigned - [ ] Components reference design system - [ ] Translations complete (SE/EN) - [ ] States documented - [ ] Conditional sections included where needed --- **Previous Step:** ← [Previous page](./previous-page.md) **Next Step:** → [Next page](./next-page.md) --- _Created using Whiteport Design Studio (WDS) methodology_ `; } async function main() { const args = parseArgs(); const { page, scenario, type } = await getInputs(args); if (!page) { console.error('Error: Page name is required.'); process.exit(1); } if (!scenario) { console.error('Error: Scenario is required.'); process.exit(1); } if (!VALID_TYPES.includes(type)) { console.error(`Error: Invalid type "${type}". Must be one of: ${VALID_TYPES.join(', ')}`); process.exit(1); } const kebab = toKebab(page); const outputDir = path.join(process.cwd(), 'design-process', 'D-UX-Design'); const outputFile = path.join(outputDir, `${kebab}.md`); fs.mkdirSync(outputDir, { recursive: true }); fs.writeFileSync(outputFile, generatePageSpec(page, scenario, type), 'utf8'); console.log(`\nPage spec created: design-process/D-UX-Design/${kebab}.md`); console.log(` Page: ${page}`); console.log(` Scenario: ${scenario}`); console.log(` Type: ${type}`); console.log('\nNext: Fill in sections, add sketches, assign Object IDs.'); } main().catch((err) => { console.error(err.message); process.exit(1); });