349 lines
9.6 KiB
JavaScript
349 lines
9.6 KiB
JavaScript
#!/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 `<!--
|
|
NAVIGATION BEST PRACTICE: Navigation links appear in THREE places:
|
|
1. Above the sketch (top)
|
|
2. Below the sketch (still in header area)
|
|
3. At document bottom
|
|
This is intentional for long specifications - users should not need to
|
|
scroll the entire document to navigate between pages.
|
|
-->
|
|
|
|
### [page-number]-${kebab}
|
|
|
|
**Previous Step:** ← [Previous page](./previous-page.md)
|
|
**Next Step:** → [Next page](./next-page.md)
|
|
|
|

|
|
|
|
**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
|
|
|
|
<!--
|
|
All spacing values MUST use token names from the project's spacing scale.
|
|
The scale is defined in E-Design-System/00-design-system.md → Spacing Scale.
|
|
Do NOT use arbitrary pixel values. If unsure, check the scale.
|
|
-->
|
|
|
|
**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
|
|
|
|
<!--
|
|
Text sizes use token names from the project's type scale.
|
|
Semantic level (H1, H2, p) signals document structure.
|
|
Visual styling (size, weight, typeface) signals visual hierarchy.
|
|
They are related but NOT locked together.
|
|
-->
|
|
|
|
**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._
|
|
|
|
<!-- When questions exist, use this format:
|
|
| # | Question | Context | Status |
|
|
|---|----------|---------|--------|
|
|
| 1 | [What needs to be decided?] | [Why it matters] | 🔴 Open |
|
|
| 2 | [Question] | [Context] | 🟢 Resolved: [answer] |
|
|
|
|
**Status Legend:** 🔴 Open | 🟡 In Discussion | 🟢 Resolved
|
|
-->
|
|
|
|
---
|
|
|
|
## 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);
|
|
});
|