create-issflow 1.9.0 → 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/cli.js +2 -2
- package/package.json +1 -1
- package/template/.issflow/README.md +1 -1
- package/template/.issflow/commands/change-request.md +2 -1
- package/template/.issflow/commands/design.md +12 -6
- package/template/.issflow/commands/phase.md +2 -0
- package/template/.issflow/commands/propose.md +80 -18
- package/template/.issflow/istartsoft-flow/METHODOLOGY.md +21 -12
- package/template/.issflow/skills/ui-ux-pro-max/LICENSE.upstream +21 -0
- package/template/.issflow/skills/ui-ux-pro-max/SKILL.md +70 -0
- package/template/.issflow/skills/ui-ux-pro-max/UPSTREAM.md +4 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/app-interface.csv +33 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/catalog-summary.json +78 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/colors.csv +193 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/data-provenance.json +1348 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/google-fonts.csv +1935 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/icons.csv +106 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/landing.csv +35 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/motion.csv +18 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/products.csv +193 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/avalonia.csv +57 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +60 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/javafx.csv +76 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/nextjs.csv +62 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +77 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +68 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/react.csv +67 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/shadcn.csv +69 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/svelte.csv +56 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/uno.csv +60 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/uwp.csv +56 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/winui.csv +60 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/stacks/wpf.csv +57 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/styles.csv +89 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/typography.csv +75 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/ui-reasoning.csv +193 -0
- package/template/.issflow/skills/ui-ux-pro-max/data/ux-guidelines.csv +120 -0
- package/template/.issflow/skills/ui-ux-pro-max/references/pro-rules.md +117 -0
- package/template/.issflow/skills/ui-ux-pro-max/references/quick-reference.md +256 -0
- package/template/.issflow/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
- package/template/.issflow/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
- package/template/.issflow/skills/ui-ux-pro-max/scripts/__pycache__/reasoning_contract.cpython-313.pyc +0 -0
- package/template/.issflow/skills/ui-ux-pro-max/scripts/core.py +993 -0
- package/template/.issflow/skills/ui-ux-pro-max/scripts/design_system.py +1668 -0
- package/template/.issflow/skills/ui-ux-pro-max/scripts/reasoning_contract.py +123 -0
- package/template/.issflow/skills/ui-ux-pro-max/scripts/search.py +177 -0
- package/template/.issflow/skills/ux-design/SKILL.md +3 -0
- package/template/.issflow/templates/UX-PACK-template.md +1 -0
- package/template/.issflow/templates/proposal.html +129 -38
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Closed, non-executable grammar for design-system decision rules."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
CONDITION_SIGNALS = {
|
|
8
|
+
"if_booking": ("booking", "appointment", "calendar"),
|
|
9
|
+
"if_boutique": ("boutique",),
|
|
10
|
+
"if_casual": ("casual", "playful"),
|
|
11
|
+
"if_checkout": ("checkout", "payment", "purchase"),
|
|
12
|
+
"if_children": ("child", "children", "kids"),
|
|
13
|
+
"if_collaboration": ("collaboration", "multiplayer", "co-edit"),
|
|
14
|
+
"if_competitive": ("competitive", "leaderboard"),
|
|
15
|
+
"if_content_focused": ("content", "article", "reading", "documentation"),
|
|
16
|
+
"if_conversion_focused": ("conversion", "sales", "signup", "purchase"),
|
|
17
|
+
"if_creative_field": ("creative", "artist", "portfolio"),
|
|
18
|
+
"if_crop_focused": ("crop", "farm", "agriculture"),
|
|
19
|
+
"if_dashboard": ("dashboard", "operations", "monitoring"),
|
|
20
|
+
"if_data_heavy": ("data heavy", "data-heavy", "analytics", "large dataset"),
|
|
21
|
+
"if_delivery": ("delivery", "courier", "shipping"),
|
|
22
|
+
"if_discovery_focused": ("discover", "discovery", "browse", "directory"),
|
|
23
|
+
"if_engagement_metric": ("engagement", "retention", "contribution"),
|
|
24
|
+
"if_experience_focused": ("experience", "immersive", "journey"),
|
|
25
|
+
"if_gamification": ("gamification", "badges", "streak"),
|
|
26
|
+
"if_health": ("health", "medical", "patient"),
|
|
27
|
+
"if_hero_needed": ("hero", "showcase", "launch"),
|
|
28
|
+
"if_large_dataset": ("large dataset", "thousands", "millions"),
|
|
29
|
+
"if_light_mode_needed": ("light mode", "light theme"),
|
|
30
|
+
"if_low_performance": ("low performance", "low-end", "slow device"),
|
|
31
|
+
"if_luxury": ("luxury", "premium", "high-end"),
|
|
32
|
+
"if_medication": ("medication", "medicine", "prescription"),
|
|
33
|
+
"if_meditation": ("meditation", "breathing", "mindfulness"),
|
|
34
|
+
"if_minimal_portfolio": ("minimal portfolio", "simple portfolio"),
|
|
35
|
+
"if_mobile": ("mobile", "phone", "tablet", "ios", "android"),
|
|
36
|
+
"if_personalized": ("personalized", "personalised", "recommendation"),
|
|
37
|
+
"if_pre_launch": ("pre-launch", "prelaunch", "coming soon", "waitlist"),
|
|
38
|
+
"if_salary_focused": ("salary", "compensation", "pay range"),
|
|
39
|
+
"if_team_collaboration": ("team collaboration", "team workspace"),
|
|
40
|
+
"if_trust_needed": ("trust", "secure", "verified", "authority"),
|
|
41
|
+
"if_ux_focused": ("ux", "usability", "accessibility", "accessible"),
|
|
42
|
+
"if_video_ready": ("video ready", "product video", "demo video"),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ALLOWED_CONDITIONS = {"must_have", *CONDITION_SIGNALS}
|
|
46
|
+
ACTION_PREFIXES = {"constraint", "style", "pattern", "mode"}
|
|
47
|
+
TOKEN_ACTION_PREFIXES = {"constraint", "style"}
|
|
48
|
+
TOKEN_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
|
|
49
|
+
CONDITION_PATTERNS = {
|
|
50
|
+
condition: tuple(
|
|
51
|
+
re.compile(r"(?<!\w)" + re.escape(signal) + r"(?!\w)")
|
|
52
|
+
for signal in signals
|
|
53
|
+
)
|
|
54
|
+
for condition, signals in CONDITION_SIGNALS.items()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _object_without_duplicates(pairs):
|
|
59
|
+
result = {}
|
|
60
|
+
for key, value in pairs:
|
|
61
|
+
if key in result:
|
|
62
|
+
raise ValueError("duplicate decision-rule key: {}".format(key))
|
|
63
|
+
result[key] = value
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def parse_decision_rules(raw):
|
|
68
|
+
"""Parse the canonical condition -> action-array representation."""
|
|
69
|
+
try:
|
|
70
|
+
rules = json.loads(raw or "{}", object_pairs_hook=_object_without_duplicates)
|
|
71
|
+
except json.JSONDecodeError as error:
|
|
72
|
+
raise ValueError("invalid decision-rule JSON: {}".format(error)) from error
|
|
73
|
+
if not isinstance(rules, dict):
|
|
74
|
+
raise ValueError("decision rules must be a JSON object")
|
|
75
|
+
for condition, actions in rules.items():
|
|
76
|
+
if condition not in ALLOWED_CONDITIONS:
|
|
77
|
+
raise ValueError("unknown decision-rule condition: {}".format(condition))
|
|
78
|
+
if not isinstance(actions, list) or not actions:
|
|
79
|
+
raise ValueError("{} must map to a non-empty action array".format(condition))
|
|
80
|
+
for action in actions:
|
|
81
|
+
_validate_action(action)
|
|
82
|
+
if len(actions) != len(set(actions)):
|
|
83
|
+
raise ValueError("{} contains duplicate actions".format(condition))
|
|
84
|
+
return rules
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _validate_action(action):
|
|
88
|
+
if not isinstance(action, str) or ":" not in action:
|
|
89
|
+
raise ValueError("action must use a known prefix: {}".format(action))
|
|
90
|
+
prefix, value = action.split(":", 1)
|
|
91
|
+
if prefix not in ACTION_PREFIXES:
|
|
92
|
+
raise ValueError("unknown decision-rule action: {}".format(action))
|
|
93
|
+
if prefix in TOKEN_ACTION_PREFIXES and not TOKEN_RE.fullmatch(value):
|
|
94
|
+
raise ValueError("invalid {} action value: {}".format(prefix, value))
|
|
95
|
+
if prefix == "pattern" and not value.strip():
|
|
96
|
+
raise ValueError("pattern action must name a pattern")
|
|
97
|
+
if prefix == "mode" and value not in {"dark", "light"}:
|
|
98
|
+
raise ValueError("mode action must be dark or light")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def apply_decision_rules(rules, query):
|
|
102
|
+
"""Return deterministic mutations and an audit trail; never execute data."""
|
|
103
|
+
normalized = str(query or "").casefold()
|
|
104
|
+
result = {"activated": [], "style_ids": [], "constraints": [],
|
|
105
|
+
"pattern": None, "mode": None}
|
|
106
|
+
for condition, actions in rules.items():
|
|
107
|
+
active = condition == "must_have" or any(
|
|
108
|
+
pattern.search(normalized)
|
|
109
|
+
for pattern in CONDITION_PATTERNS.get(condition, ()))
|
|
110
|
+
if not active:
|
|
111
|
+
continue
|
|
112
|
+
result["activated"].append({"condition": condition, "actions": list(actions)})
|
|
113
|
+
for action in actions:
|
|
114
|
+
prefix, value = action.split(":", 1)
|
|
115
|
+
if prefix == "style" and value not in result["style_ids"]:
|
|
116
|
+
result["style_ids"].append(value)
|
|
117
|
+
elif prefix == "constraint" and value not in result["constraints"]:
|
|
118
|
+
result["constraints"].append(value)
|
|
119
|
+
elif prefix == "pattern":
|
|
120
|
+
result["pattern"] = value
|
|
121
|
+
elif prefix == "mode":
|
|
122
|
+
result["mode"] = value
|
|
123
|
+
return result
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
UI/UX Pro Max Search - BM25 search engine for UI/UX style guides
|
|
5
|
+
Usage: python search.py "<query>" [--domain <domain>] [--stack <stack>] [--max-results 3]
|
|
6
|
+
python search.py "<query>" --design-system [-p "Project Name"]
|
|
7
|
+
python search.py "<query>" --design-system --persist [-p "Project Name"] --output-dir "<project-root>" [--page "dashboard"]
|
|
8
|
+
python search.py "<query>" --design-system --variance 8 --motion 9 --density 7
|
|
9
|
+
|
|
10
|
+
Domains: style, color, chart, landing, product, ux, typography, google-fonts, icons, gsap, react, web
|
|
11
|
+
Stacks: react, nextjs, vue, svelte, astro, swiftui, react-native, flutter, nuxtjs, nuxt-ui,
|
|
12
|
+
html-tailwind, shadcn, jetpack-compose, threejs, angular, laravel
|
|
13
|
+
|
|
14
|
+
Design dials (1-10, only with --design-system):
|
|
15
|
+
--variance DESIGN_VARIANCE: 1=centered/minimal, 10=bold/asymmetric
|
|
16
|
+
--motion MOTION_INTENSITY: 1=subtle, 10=complex; attaches a GSAP snippet from motion.csv
|
|
17
|
+
--density VISUAL_DENSITY: 1=spacious, 10=dense/dashboard; overrides the spacing scale
|
|
18
|
+
|
|
19
|
+
Persistence (Master + Overrides pattern):
|
|
20
|
+
--persist Save design system to design-system/<project-slug>/MASTER.md
|
|
21
|
+
--output-dir Directory the design-system/ folder is created under (defaults to cwd --
|
|
22
|
+
always pass this explicitly, pointed at the project root)
|
|
23
|
+
--page Also create a page-specific override file in design-system/<project-slug>/pages/
|
|
24
|
+
--force Overwrite an existing MASTER.md (without this, persistence is skipped
|
|
25
|
+
if MASTER.md already exists, so prior design decisions aren't lost)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import json as json_module
|
|
30
|
+
import sys
|
|
31
|
+
import io
|
|
32
|
+
from core import CSV_CONFIG, AVAILABLE_STACKS, MAX_RESULTS, UNTRUNCATED_COLS, search, search_stack
|
|
33
|
+
from design_system import generate_design_system
|
|
34
|
+
|
|
35
|
+
# Force UTF-8 for stdout/stderr to handle emojis on Windows (cp1252 default)
|
|
36
|
+
if sys.stdout.encoding and sys.stdout.encoding.lower() != 'utf-8':
|
|
37
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
38
|
+
if sys.stderr.encoding and sys.stderr.encoding.lower() != 'utf-8':
|
|
39
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
|
40
|
+
|
|
41
|
+
TRUNCATE_AT = 300
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def format_output(result, full=False):
|
|
45
|
+
"""Format results for Claude consumption (token-optimized)"""
|
|
46
|
+
if "error" in result:
|
|
47
|
+
return f"Error: {result['error']}"
|
|
48
|
+
|
|
49
|
+
output = []
|
|
50
|
+
if result.get("stack"):
|
|
51
|
+
output.append("## UI Pro Max Stack Guidelines")
|
|
52
|
+
output.append(f"**Stack:** {result['stack']} | **Query:** {result['query']}")
|
|
53
|
+
else:
|
|
54
|
+
output.append("## UI Pro Max Search Results")
|
|
55
|
+
domain_note = result['domain']
|
|
56
|
+
if result.get("auto_detected"):
|
|
57
|
+
domain_note += " (auto-detected"
|
|
58
|
+
if result.get("runner_up_domain"):
|
|
59
|
+
domain_note += f", runner-up: {result['runner_up_domain']}"
|
|
60
|
+
domain_note += ")"
|
|
61
|
+
output.append(f"**Domain:** {domain_note} | **Query:** {result['query']}")
|
|
62
|
+
output.append(f"**Source:** {result['file']} | **Found:** {result['count']} results\n")
|
|
63
|
+
|
|
64
|
+
if result['count'] == 0:
|
|
65
|
+
redirect = result.get("redirect")
|
|
66
|
+
if redirect:
|
|
67
|
+
output.append(
|
|
68
|
+
"This legacy style label is now modeled in the "
|
|
69
|
+
f"`{redirect['domain']}` domain as `{redirect['id']}`. "
|
|
70
|
+
"Search that domain instead of treating a page composition as a visual style."
|
|
71
|
+
)
|
|
72
|
+
return "\n".join(output)
|
|
73
|
+
output.append(
|
|
74
|
+
"No matches. This is not a match with an empty value -- the query "
|
|
75
|
+
"did not hit the database. Retry with broader/different keywords "
|
|
76
|
+
"before falling back to general defaults, and say explicitly that "
|
|
77
|
+
"no database match was found if you do fall back."
|
|
78
|
+
)
|
|
79
|
+
suggestions = result.get("suggestions") or []
|
|
80
|
+
if suggestions:
|
|
81
|
+
output.append(f"**Closest known terms:** {', '.join(suggestions)}")
|
|
82
|
+
return "\n".join(output)
|
|
83
|
+
|
|
84
|
+
for i, row in enumerate(result['results'], 1):
|
|
85
|
+
output.append(f"### Result {i}")
|
|
86
|
+
for key, value in row.items():
|
|
87
|
+
value_str = str(value)
|
|
88
|
+
if not full and key not in UNTRUNCATED_COLS and len(value_str) > TRUNCATE_AT:
|
|
89
|
+
value_str = value_str[:TRUNCATE_AT] + "..."
|
|
90
|
+
output.append(f"- **{key}:** {value_str}")
|
|
91
|
+
output.append("")
|
|
92
|
+
|
|
93
|
+
return "\n".join(output)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if __name__ == "__main__":
|
|
97
|
+
parser = argparse.ArgumentParser(description="UI Pro Max Search")
|
|
98
|
+
parser.add_argument("query", help="Search query")
|
|
99
|
+
parser.add_argument("--domain", "-d", choices=list(CSV_CONFIG.keys()), help="Search domain")
|
|
100
|
+
parser.add_argument("--stack", "-s", choices=AVAILABLE_STACKS, help=f"Stack-specific search. Available: {', '.join(AVAILABLE_STACKS)}")
|
|
101
|
+
parser.add_argument("--max-results", "-n", type=int, choices=range(1, 21), default=MAX_RESULTS,
|
|
102
|
+
metavar="1-20", help="Max results (default: 3)")
|
|
103
|
+
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
104
|
+
parser.add_argument("--full", action="store_true", help="Do not truncate long field values in text output")
|
|
105
|
+
# Design system generation
|
|
106
|
+
parser.add_argument("--design-system", "-ds", action="store_true", help="Generate complete design system recommendation")
|
|
107
|
+
parser.add_argument("--project-name", "-p", type=str, default=None, help="Project name for design system output")
|
|
108
|
+
parser.add_argument("--format", "-f", choices=["ascii", "markdown"], default="ascii", help="Output format for design system (ignored if --json)")
|
|
109
|
+
# Persistence (Master + Overrides pattern)
|
|
110
|
+
parser.add_argument("--persist", action="store_true", help="Save design system to design-system/<project-slug>/MASTER.md (creates hierarchical structure)")
|
|
111
|
+
parser.add_argument("--page", type=str, default=None, help="Create page-specific override file in design-system/<project-slug>/pages/")
|
|
112
|
+
parser.add_argument("--output-dir", "-o", type=str, default=None, help="Output directory for persisted files (default: current directory -- pass this explicitly, pointed at the project root)")
|
|
113
|
+
parser.add_argument("--force", action="store_true", help="Overwrite an existing MASTER.md when persisting (default: skip if it already exists)")
|
|
114
|
+
# Design dials (1-10), only applied with --design-system
|
|
115
|
+
parser.add_argument("--variance", type=int, choices=range(1, 11), metavar="1-10", help="DESIGN_VARIANCE dial: 1=centered/minimal, 10=bold/asymmetric (only with --design-system)")
|
|
116
|
+
parser.add_argument("--motion", type=int, choices=range(1, 11), metavar="1-10", help="MOTION_INTENSITY dial: 1=subtle, 10=complex; pulls a matching GSAP snippet from motion.csv (only with --design-system)")
|
|
117
|
+
parser.add_argument("--density", type=int, choices=range(1, 11), metavar="1-10", help="VISUAL_DENSITY dial: 1=spacious, 10=dense/dashboard; overrides the spacing scale (only with --design-system)")
|
|
118
|
+
|
|
119
|
+
args = parser.parse_args()
|
|
120
|
+
|
|
121
|
+
# Design system takes priority
|
|
122
|
+
if args.design_system:
|
|
123
|
+
if args.stack:
|
|
124
|
+
print(
|
|
125
|
+
f"note: --stack {args.stack} is ignored in --design-system mode; "
|
|
126
|
+
"run a separate --stack query for stack-specific guidelines",
|
|
127
|
+
file=sys.stderr,
|
|
128
|
+
)
|
|
129
|
+
result = generate_design_system(
|
|
130
|
+
args.query,
|
|
131
|
+
args.project_name,
|
|
132
|
+
args.format,
|
|
133
|
+
persist=args.persist,
|
|
134
|
+
page=args.page,
|
|
135
|
+
output_dir=args.output_dir,
|
|
136
|
+
variance=args.variance,
|
|
137
|
+
motion=args.motion,
|
|
138
|
+
density=args.density,
|
|
139
|
+
force=args.force,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if args.json:
|
|
143
|
+
print(json_module.dumps(
|
|
144
|
+
{"design_system": result["design_system"], "persistence": result["persistence"]},
|
|
145
|
+
indent=2, ensure_ascii=False,
|
|
146
|
+
))
|
|
147
|
+
else:
|
|
148
|
+
print(result["text"])
|
|
149
|
+
|
|
150
|
+
if args.persist:
|
|
151
|
+
persistence = result["persistence"] or {}
|
|
152
|
+
print("\n" + "=" * 60)
|
|
153
|
+
if persistence.get("status") == "skipped_exists":
|
|
154
|
+
print(f"⚠️ {persistence.get('message', 'MASTER.md already exists; not overwritten.')}")
|
|
155
|
+
else:
|
|
156
|
+
ds_dir = persistence.get("design_system_dir", "design-system/<project>")
|
|
157
|
+
print(f"✅ Design system persisted to {ds_dir}/")
|
|
158
|
+
for f in persistence.get("created_files", []):
|
|
159
|
+
print(f" 📄 {f}")
|
|
160
|
+
print("")
|
|
161
|
+
print(f"📖 Usage: When building a page, check {ds_dir}/pages/[page].md first.")
|
|
162
|
+
print(" If it exists, its rules override MASTER.md. Otherwise, use MASTER.md.")
|
|
163
|
+
print("=" * 60)
|
|
164
|
+
# Stack search
|
|
165
|
+
elif args.stack:
|
|
166
|
+
result = search_stack(args.query, args.stack, args.max_results)
|
|
167
|
+
if args.json:
|
|
168
|
+
print(json_module.dumps(result, indent=2, ensure_ascii=False))
|
|
169
|
+
else:
|
|
170
|
+
print(format_output(result, full=args.full))
|
|
171
|
+
# Domain search
|
|
172
|
+
else:
|
|
173
|
+
result = search(args.query, args.domain, args.max_results)
|
|
174
|
+
if args.json:
|
|
175
|
+
print(json_module.dumps(result, indent=2, ensure_ascii=False))
|
|
176
|
+
else:
|
|
177
|
+
print(format_output(result, full=args.full))
|
|
@@ -29,6 +29,9 @@ confirmed with the user before building — design is where human taste matters.
|
|
|
29
29
|
|
|
30
30
|
- **Authoring the UX pack** (`/design` → `docs/UX-PACK.md`) after proposal sign-off
|
|
31
31
|
and before `/phase` (hard rule 15) — produce the frame the build must stay inside.
|
|
32
|
+
Visual direction (style, palette, type) comes from `ui-ux-pro-max` first; this
|
|
33
|
+
skill still owns the wireframe + cookbook. Do not skip the cookbook because a
|
|
34
|
+
catalogue picked a look.
|
|
32
35
|
- Building a screen / page / component.
|
|
33
36
|
- Reviewing a UI diff (the "ตรวจ" pass).
|
|
34
37
|
- CLOSE gate of any frontend phase — the cookbook check MUST pass.
|
|
@@ -22,6 +22,7 @@ must stay inside. Wireframe baseline first; visual direction confirmed here.
|
|
|
22
22
|
## Frame & tokens
|
|
23
23
|
- Wireframe baseline: (link or summary)
|
|
24
24
|
- Brand / tokens: (or “follow OVERVIEW UX direction”)
|
|
25
|
+
- UI UX Pro Max: style · palette · type · anti-patterns
|
|
25
26
|
- Breakpoints: XS · SM · MD · LG · XL
|
|
26
27
|
|
|
27
28
|
## States & a11y notes
|
|
@@ -1,12 +1,51 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<!--
|
|
3
|
-
Client-facing proposal
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
Client-facing proposal (rendered by /propose from docs/PROPOSAL.md).
|
|
4
|
+
|
|
5
|
+
PAGE 1 = the only signable A4 sheet (packages 3–6 rows, short scope). Trim
|
|
6
|
+
sign-page copy before it overflows. Screen sheets after the first page-break
|
|
7
|
+
may grow extra A4 pages.
|
|
8
|
+
|
|
9
|
+
HOW TO USE (agent): copy to docs/proposal.html, replace every {{PLACEHOLDER}}.
|
|
10
|
+
Localise CONTENT to the project language; section labels are bilingual (TH / EN).
|
|
11
|
+
WHITE-LABEL: fill {{COMPANY}} / {{LOGO}} with the ISSUING company's brand (not
|
|
12
|
+
the kit's). {{LOGO}} optional: an <img src="…">, an SVG, or delete the monogram.
|
|
13
|
+
{{BANK_COL}}: omit unless the human gave bank details (no kit default).
|
|
14
|
+
{{PACKAGE_TABLE}}: default head = ชุดงาน / Packages + Cost; if hours columns
|
|
15
|
+
were requested = ประเมิน / คิดเงิน / ยอด. Never leave sample amounts in the file.
|
|
16
|
+
{{SCREEN_SHEETS}}: one .sheet.screens per appendix page; cover every in-scope
|
|
17
|
+
module from PLAN + OVERVIEW; numbered captions; no emoji icons; not pixel-binding.
|
|
18
|
+
{{PAGE_FOOTER}}: real page count (sign page 1 of N).
|
|
19
|
+
Print: Ctrl/Cmd-P → A4 → "Save as PDF".
|
|
20
|
+
|
|
21
|
+
Example screen frame (fill {{SCREEN_SHEETS}}; placeholders only — no domain copy):
|
|
22
|
+
<div class="sheet screens">
|
|
23
|
+
<div class="head">… {{COMPANY}} · ตัวอย่างจอ — ไม่ผูกพิกเซล / Screens — not pixel-binding</div>
|
|
24
|
+
<div class="modbar"><span class="here">{{MODULE}}</span></div>
|
|
25
|
+
<p class="note">{{SCREEN_NOTE}}</p>
|
|
26
|
+
<div class="figs">
|
|
27
|
+
<figure>
|
|
28
|
+
<div class="desk">
|
|
29
|
+
<div class="top"><span class="mark">{{MARK}}</span><span class="brand">{{APP_NAME}}</span></div>
|
|
30
|
+
<div class="body">
|
|
31
|
+
<div class="nav"><span class="on">{{NAV_ON}}</span></div>
|
|
32
|
+
<div class="main">
|
|
33
|
+
<div class="kpis"><div class="card">{{CARD}}</div></div>
|
|
34
|
+
<table class="ui">{{UI_ROWS}}</table>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<figcaption><b>{{FRAME_N}}. {{FRAME_TITLE}}</b> — {{FRAME_CAPTION}}</figcaption>
|
|
39
|
+
</figure>
|
|
40
|
+
<figure>
|
|
41
|
+
<div class="phone">
|
|
42
|
+
<div class="top"><span class="mark">{{MARK}}</span><span class="brand">{{APP_NAME}}</span></div>
|
|
43
|
+
<div class="screen">{{PHONE_BODY}}</div>
|
|
44
|
+
</div>
|
|
45
|
+
<figcaption><b>{{FRAME_N}}. {{FRAME_TITLE}}</b> — {{FRAME_CAPTION}}</figcaption>
|
|
46
|
+
</figure>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
10
49
|
-->
|
|
11
50
|
<html lang="{{LANG}}">
|
|
12
51
|
<head>
|
|
@@ -16,23 +55,21 @@
|
|
|
16
55
|
@page { size: A4; margin: 0 }
|
|
17
56
|
:root{
|
|
18
57
|
--ink:#162033; --muted:#5d6a7d; --line:#dbe3ee; --soft:#f6f8fb;
|
|
19
|
-
--violet:#635bff; --green:#15803d;
|
|
58
|
+
--violet:#635bff; --green:#15803d; --bad:#b42318;
|
|
20
59
|
--sans:"Inter","Noto Sans Thai",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
|
|
21
60
|
--mono:ui-monospace,Menlo,Consolas,monospace;
|
|
22
61
|
}
|
|
23
62
|
*{box-sizing:border-box}
|
|
24
|
-
body{margin:0;background:#e9edf3;color:var(--ink);font-family:var(--sans);font-size:10.5px;line-height:1.
|
|
25
|
-
.sheet{width:210mm;min-height:297mm;margin:10mm auto;background:#fff;padding:15mm
|
|
26
|
-
.lbl{color:var(--muted);font-size:
|
|
63
|
+
body{margin:0;background:#e9edf3;color:var(--ink);font-family:var(--sans);font-size:10.5px;line-height:1.4}
|
|
64
|
+
.sheet{width:210mm;min-height:297mm;margin:10mm auto;background:#fff;padding:13mm 15mm;display:flex;flex-direction:column;gap:8px}
|
|
65
|
+
.lbl{color:var(--muted);font-size:8px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;margin:0 0 3px}
|
|
27
66
|
.lbl em{font-style:normal;opacity:.6;font-weight:600}
|
|
28
|
-
h1{font-size:
|
|
29
|
-
.muted{color:var(--muted)} ul{margin:0;padding-left:15px} li{margin:
|
|
30
|
-
hr{border:none;border-top:1px solid var(--line);margin:0}
|
|
67
|
+
h1{font-size:19px;margin:0;letter-spacing:-.3px;line-height:1.15}
|
|
68
|
+
.muted{color:var(--muted)} ul{margin:0;padding-left:15px} li{margin:1px 0}
|
|
31
69
|
|
|
32
|
-
|
|
33
|
-
.head{display:flex;justify-content:space-between;align-items:flex-start;border-bottom:2px solid var(--ink);padding-bottom:11px}
|
|
70
|
+
.head{display:flex;justify-content:space-between;align-items:flex-start;border-bottom:2px solid var(--ink);padding-bottom:9px}
|
|
34
71
|
.co{display:flex;align-items:center;gap:10px}
|
|
35
|
-
.logo{width:
|
|
72
|
+
.logo{width:40px;height:40px;border-radius:9px;background:var(--ink);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:13px;flex:0 0 auto;overflow:hidden}
|
|
36
73
|
.logo img{width:100%;height:100%;object-fit:contain}
|
|
37
74
|
.co .name{font-weight:800;font-size:15px;line-height:1.15}
|
|
38
75
|
.co .tag{color:var(--muted);font-size:9px}
|
|
@@ -40,19 +77,69 @@
|
|
|
40
77
|
.head .meta .big{font:800 12px/1.2 var(--sans);letter-spacing:.12em;color:var(--violet)}
|
|
41
78
|
.pill{display:inline-block;border:1px solid var(--line);border-radius:999px;padding:2px 8px;font:800 8px/1 var(--mono);color:var(--muted);margin-top:4px}
|
|
42
79
|
|
|
43
|
-
.cols{display:flex;gap:
|
|
44
|
-
table{width:100%;border-collapse:collapse;font-size:10px}
|
|
45
|
-
th
|
|
46
|
-
th{background:var(--soft);font-size:8px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
|
|
47
|
-
td.n
|
|
48
|
-
tfoot td{font-weight:800;border-top:1.5px solid var(--ink);border-bottom:none}
|
|
49
|
-
.total{display:flex;align-items:
|
|
50
|
-
.total .amt{font-size:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
.sign
|
|
54
|
-
|
|
55
|
-
|
|
80
|
+
.cols{display:flex;gap:16px} .cols>div{flex:1;min-width:0}
|
|
81
|
+
table.quote{width:100%;border-collapse:collapse;font-size:10px}
|
|
82
|
+
.quote th,.quote td{text-align:left;padding:4px 6px;border-bottom:1px solid var(--line)}
|
|
83
|
+
.quote th{background:var(--soft);font-size:8px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
|
|
84
|
+
.quote td.n,.quote th.n{text-align:right;font-family:var(--mono)}
|
|
85
|
+
.quote tfoot td{font-weight:800;border-top:1.5px solid var(--ink);border-bottom:none}
|
|
86
|
+
.total{display:flex;align-items:flex-end;justify-content:space-between;background:var(--soft);border-radius:9px;padding:8px 12px;gap:12px}
|
|
87
|
+
.total .amt{font-size:18px;font-weight:800;text-align:right}
|
|
88
|
+
.total .amt small{display:block;font-size:9px;color:var(--muted);font-weight:600}
|
|
89
|
+
|
|
90
|
+
.sign{display:flex;gap:34px;margin-top:auto;padding-top:10px}
|
|
91
|
+
.sign>div{flex:1} .line{border-bottom:1px solid var(--ink);height:26px} .sign .lbl{margin-top:4px}
|
|
92
|
+
footer{border-top:1px solid var(--line);padding-top:7px;color:var(--muted);font-size:8px;display:flex;justify-content:space-between}
|
|
93
|
+
|
|
94
|
+
/* screen appendix — empty chrome; fill from PLAN + OVERVIEW */
|
|
95
|
+
.sheet.screens{padding:10mm 12mm;gap:6px}
|
|
96
|
+
.screens .head{padding-bottom:6px}
|
|
97
|
+
.screens .note{font-size:9px;margin:0;color:var(--muted)}
|
|
98
|
+
.modbar{display:flex;flex-wrap:wrap;gap:4px}
|
|
99
|
+
.modbar span{border:1px solid var(--line);border-radius:999px;padding:2px 7px;font-size:8px;font-weight:700;color:var(--muted)}
|
|
100
|
+
.modbar .here{background:#ecebff;border-color:#d4d1ff;color:var(--violet)}
|
|
101
|
+
.figs{display:grid;grid-template-columns:1fr 1fr;gap:8px 10px;flex:1}
|
|
102
|
+
figure{margin:0;display:flex;flex-direction:column;min-width:0}
|
|
103
|
+
figcaption{margin-top:3px;font-size:8px;color:var(--muted);line-height:1.35}
|
|
104
|
+
figcaption b{color:var(--ink);font-weight:700}
|
|
105
|
+
.desk,.phone{background:#fff;border:1px solid var(--line);border-radius:10px;overflow:hidden;flex:1;min-height:0}
|
|
106
|
+
.top{display:flex;align-items:center;gap:6px;padding:5px 8px;border-bottom:1px solid var(--line);background:var(--soft)}
|
|
107
|
+
.mark{width:16px;height:16px;border-radius:4px;background:var(--violet);color:#fff;font-size:8px;font-weight:800;display:flex;align-items:center;justify-content:center}
|
|
108
|
+
.brand{font-weight:800;font-size:10px;color:var(--violet)}
|
|
109
|
+
.sub{font-size:8px;color:var(--muted)}
|
|
110
|
+
.body{display:flex;min-height:148px}
|
|
111
|
+
.nav{width:64px;border-right:1px solid var(--line);padding:5px;font-size:8px}
|
|
112
|
+
.nav span{display:block;padding:2px 4px;border-radius:4px;margin-bottom:1px;color:#334155}
|
|
113
|
+
.nav .on{background:#ecebff;color:var(--violet);font-weight:700}
|
|
114
|
+
.nav .tail{margin-top:6px;padding-top:4px;border-top:1px solid var(--line)}
|
|
115
|
+
.main{flex:1;padding:6px;min-width:0}
|
|
116
|
+
.kpis{display:grid;grid-template-columns:1fr 1fr 1fr;gap:4px}
|
|
117
|
+
.kpis.two{grid-template-columns:1fr 1fr}
|
|
118
|
+
.card{border:1px solid var(--line);border-radius:6px;padding:4px 5px;font-size:8px;background:#fff}
|
|
119
|
+
.card .n{font-size:12px;font-weight:800}
|
|
120
|
+
.red{border-color:#f0c4c0;background:#fff7f6}
|
|
121
|
+
.red .n{color:var(--bad)}
|
|
122
|
+
.ok{border-color:#b7d4c2;background:#f4faf6}
|
|
123
|
+
.ok .n{color:var(--green)}
|
|
124
|
+
.warn{border-color:#ead8a4;background:#fffbeb}
|
|
125
|
+
.ui{width:100%;border-collapse:collapse;font-size:8px;margin-top:4px}
|
|
126
|
+
.ui th,.ui td{text-align:left;padding:2px 3px;border-bottom:1px solid var(--line)}
|
|
127
|
+
.ui th{color:var(--muted);font-weight:600}
|
|
128
|
+
.btn{display:inline-block;background:var(--violet);color:#fff;border-radius:4px;padding:3px 7px;font-size:8px;font-weight:700;margin-top:5px}
|
|
129
|
+
.btn.ghost{background:#fff;color:var(--violet);border:1px solid var(--violet)}
|
|
130
|
+
.field{display:flex;justify-content:space-between;gap:8px;border-bottom:1px dashed var(--line);padding:3px 0;font-size:8px}
|
|
131
|
+
.crumbs{font-size:8px;color:var(--muted);margin-bottom:3px}
|
|
132
|
+
.login{padding:12px;min-height:148px;display:flex;flex-direction:column;justify-content:center;align-items:flex-start;gap:6px}
|
|
133
|
+
.login h3{margin:0;font-size:12px}
|
|
134
|
+
.phone{max-width:168px;margin:0 auto;width:100%}
|
|
135
|
+
.screen{padding:7px}
|
|
136
|
+
.tiny{font-size:7px;color:var(--muted)}
|
|
137
|
+
|
|
138
|
+
@media print{
|
|
139
|
+
body{background:#fff}
|
|
140
|
+
.sheet{margin:0;min-height:297mm;page-break-after:always;box-shadow:none}
|
|
141
|
+
.sheet:last-child{page-break-after:auto}
|
|
142
|
+
}
|
|
56
143
|
</style>
|
|
57
144
|
</head>
|
|
58
145
|
<body>
|
|
@@ -81,12 +168,8 @@
|
|
|
81
168
|
</div>
|
|
82
169
|
|
|
83
170
|
<div>
|
|
84
|
-
<p class="lbl"
|
|
85
|
-
<table>
|
|
86
|
-
<thead><tr><th>เฟส <em style="font-style:normal;opacity:.5">Phase</em></th><th>Complexity</th><th class="n">Effort</th><th class="n">Cost</th></tr></thead>
|
|
87
|
-
<tbody>{{PHASE_ROWS}}<!-- <tr><td>1. Auth</td><td>M</td><td class="n">2–3d</td><td class="n">฿16k–24k</td></tr> --></tbody>
|
|
88
|
-
<tfoot><tr><td colspan="2">รวม <em style="font-style:normal;opacity:.5">Total</em></td><td class="n">{{TOTAL_EFFORT}}</td><td class="n">{{TOTAL_COST}}</td></tr></tfoot>
|
|
89
|
-
</table>
|
|
171
|
+
<p class="lbl">ชุดงาน & ค่าใช้จ่าย <em>Packages & cost</em></p>
|
|
172
|
+
<table class="quote">{{PACKAGE_TABLE}}</table>
|
|
90
173
|
</div>
|
|
91
174
|
|
|
92
175
|
<div class="total">
|
|
@@ -100,14 +183,22 @@
|
|
|
100
183
|
<div><p class="lbl">ข้อยกเว้น <em>Exclusions</em></p><ul>{{EXCLUSION_ITEMS}}</ul></div>
|
|
101
184
|
</div>
|
|
102
185
|
|
|
103
|
-
<div
|
|
186
|
+
<div class="cols">
|
|
187
|
+
<div>
|
|
188
|
+
<p class="lbl">เงื่อนไขการชำระเงิน <em>Payment</em></p>
|
|
189
|
+
<ul>{{PAYMENT_ITEMS}}</ul>
|
|
190
|
+
</div>
|
|
191
|
+
{{BANK_COL}}
|
|
192
|
+
</div>
|
|
104
193
|
|
|
105
194
|
<div class="sign">
|
|
106
195
|
<div><div class="line"></div><p class="lbl">ลงนามลูกค้า <em>Client sign-off</em></p></div>
|
|
107
196
|
<div><div class="line"></div><p class="lbl">วันที่ <em>Date</em></p></div>
|
|
108
197
|
</div>
|
|
109
198
|
|
|
110
|
-
<footer><span>{{COMPANY}} · {{
|
|
199
|
+
<footer><span>{{COMPANY}} · {{PAGE_FOOTER}}</span><span>{{VERSION}}</span></footer>
|
|
111
200
|
</div>
|
|
201
|
+
|
|
202
|
+
{{SCREEN_SHEETS}}
|
|
112
203
|
</body>
|
|
113
204
|
</html>
|