popilot 0.3.0 → 0.5.0
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/lib/hydrate.mjs +6 -1
- package/lib/setup-wizard.mjs +29 -3
- package/package.json +1 -1
- package/scaffold/.claude/commands/_domain.md.hbs +33 -0
- package/scaffold/.claude/commands/analytics.md.hbs +55 -0
- package/scaffold/.claude/commands/daily.md.hbs +301 -0
- package/scaffold/.claude/commands/dev.md.hbs +62 -0
- package/scaffold/.claude/commands/gtm.md +82 -0
- package/scaffold/.claude/commands/handoff.md +259 -0
- package/scaffold/.claude/commands/market.md +120 -0
- package/scaffold/.claude/commands/metrics.md +123 -0
- package/scaffold/.claude/commands/oscar-loop.md +436 -0
- package/scaffold/.claude/commands/party.md +85 -0
- package/scaffold/.claude/commands/plan.md +43 -0
- package/scaffold/.claude/commands/poc.md +69 -0
- package/scaffold/.claude/commands/research.md +203 -0
- package/scaffold/.claude/commands/retro.md +68 -0
- package/scaffold/.claude/commands/save.md +440 -0
- package/scaffold/.claude/commands/sessions.md +139 -0
- package/scaffold/.claude/commands/sprint.md +106 -0
- package/scaffold/.claude/commands/start.md +396 -0
- package/scaffold/.claude/commands/strategy.md +41 -0
- package/scaffold/.claude/commands/task.md +220 -0
- package/scaffold/.claude/commands/tracking.md +116 -0
- package/scaffold/.claude/commands/validate.md +58 -0
- package/scaffold/.context/WORKFLOW.md.hbs +58 -26
- package/scaffold/.context/agents/planner.md.hbs +35 -7
- package/scaffold/.context/integrations/_registry.yaml +6 -0
- package/scaffold/.context/integrations/providers/sqlite_lambda.yaml +24 -0
- package/scaffold/.context/integrations/providers/supabase.yaml +34 -0
- package/scaffold/.context/integrations/providers/turso_cf.yaml +34 -0
- package/scaffold/.context/poc/_skills/build.md +79 -0
- package/scaffold/.context/poc/_skills/scope.md +50 -0
- package/scaffold/.context/poc/_skills/spec.md +80 -0
- package/scaffold/.context/poc/_skills/verify.md +60 -0
- package/scaffold/CLAUDE.md.hbs +210 -0
- package/scaffold/spec-site/.env.example +11 -0
- package/scaffold/spec-site/index.html +2 -2
- package/scaffold/spec-site/sql/schema.sql +224 -0
- package/scaffold/spec-site/src/api/client.ts +131 -0
- package/scaffold/spec-site/src/api/types.ts +177 -0
- package/scaffold/spec-site/src/components/Accordion.vue +1 -1
- package/scaffold/spec-site/src/components/AppHeader.vue +5 -4
- package/scaffold/spec-site/src/components/CoachingCard.vue +1 -1
- package/scaffold/spec-site/src/components/ScenarioSwitcher.vue +1 -1
- package/scaffold/spec-site/src/composables/navTypes.ts +39 -0
- package/scaffold/spec-site/src/composables/pmTypes.ts +134 -0
- package/scaffold/spec-site/src/composables/useAuth.ts +139 -0
- package/scaffold/spec-site/src/composables/useMemo.ts +51 -40
- package/scaffold/spec-site/src/composables/useNavStore.ts +202 -0
- package/scaffold/spec-site/src/composables/usePageContent.ts +208 -0
- package/scaffold/spec-site/src/composables/usePmStore.ts +224 -0
- package/scaffold/spec-site/src/composables/useRetro.ts +181 -95
- package/scaffold/spec-site/src/composables/useScenarioStore.ts +74 -30
- package/scaffold/spec-site/src/composables/useUser.ts +12 -6
- package/scaffold/spec-site/src/data/navigation.ts +7 -42
- package/scaffold/spec-site/src/data/types.ts +13 -43
- package/scaffold/spec-site/src/main.ts +7 -0
- package/scaffold/spec-site/src/pages/PolicyDetail.vue +30 -11
- package/scaffold/spec-site/src/pages/PolicyIndex.vue +22 -7
- package/scaffold/spec-site/src/pages/retro/RetroActions.vue +3 -3
- package/scaffold/spec-site/src/pages/retro/RetroBoard.vue +2 -2
- package/scaffold/spec-site/src/pages/retro/RetroHeader.vue +5 -7
- package/scaffold/spec-site/src/pages/retro/RetroPage.vue +2 -2
- package/scaffold/spec-site/src/pages/shared/NoContentPlaceholder.vue +2 -2
- package/scaffold/spec-site/src/pages/shared/PolicyFallback.vue +25 -13
- package/scaffold/spec-site/src/router.ts +11 -7
- package/scaffold/spec-site/src/styles/base.css +2 -2
- package/scaffold/spec-site/src/styles/split-pane.css +1 -1
- package/scaffold/spec-site/src/styles/variables.css +7 -7
- package/scaffold/spec-site/src/assets/icons/menu/ic_ads.svg +0 -10
- package/scaffold/spec-site/src/assets/icons/menu/ic_ads_on.svg +0 -10
- package/scaffold/spec-site/src/assets/icons/menu/ic_board.svg +0 -14
- package/scaffold/spec-site/src/assets/icons/menu/ic_board_on.svg +0 -14
- package/scaffold/spec-site/src/assets/icons/menu/ic_dashboard.svg +0 -21
- package/scaffold/spec-site/src/assets/icons/menu/ic_dashboard_on.svg +0 -21
- package/scaffold/spec-site/src/assets/icons/menu/ic_pricing.svg +0 -20
- package/scaffold/spec-site/src/assets/icons/menu/ic_pricing_on.svg +0 -20
- package/scaffold/spec-site/src/assets/icons/menu/ic_store.svg +0 -11
- package/scaffold/spec-site/src/assets/icons/menu/ic_store_on.svg +0 -11
- package/scaffold/spec-site/src/composables/useTurso.ts +0 -160
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
-- spec-site database schema
|
|
2
|
+
-- Compatible with: SQLite, Turso/LibSQL, PostgreSQL (with minor adjustments)
|
|
3
|
+
--
|
|
4
|
+
-- This schema supports the full interactive mode (Tier 2) of spec-site.
|
|
5
|
+
-- Run this migration to set up your backend database.
|
|
6
|
+
|
|
7
|
+
-- ── Navigation ──
|
|
8
|
+
|
|
9
|
+
CREATE TABLE IF NOT EXISTS sprints (
|
|
10
|
+
id TEXT PRIMARY KEY,
|
|
11
|
+
label TEXT NOT NULL,
|
|
12
|
+
theme TEXT NOT NULL DEFAULT '',
|
|
13
|
+
active INTEGER NOT NULL DEFAULT 0,
|
|
14
|
+
start_date TEXT,
|
|
15
|
+
end_date TEXT,
|
|
16
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
17
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
18
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
CREATE TABLE IF NOT EXISTS nav_epics (
|
|
22
|
+
sprint TEXT NOT NULL,
|
|
23
|
+
epic_id TEXT NOT NULL,
|
|
24
|
+
label TEXT NOT NULL,
|
|
25
|
+
badge TEXT,
|
|
26
|
+
category TEXT NOT NULL DEFAULT 'policy',
|
|
27
|
+
description TEXT,
|
|
28
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
uid TEXT,
|
|
30
|
+
origin_sprint TEXT,
|
|
31
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
32
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
33
|
+
PRIMARY KEY (sprint, epic_id)
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
-- ── PM (Project Management) ──
|
|
37
|
+
|
|
38
|
+
CREATE TABLE IF NOT EXISTS pm_epics (
|
|
39
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
40
|
+
title TEXT NOT NULL,
|
|
41
|
+
description TEXT,
|
|
42
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
43
|
+
owner TEXT,
|
|
44
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
45
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS pm_stories (
|
|
49
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
50
|
+
epic_id INTEGER REFERENCES pm_epics(id),
|
|
51
|
+
sprint TEXT NOT NULL,
|
|
52
|
+
epic_uid TEXT NOT NULL DEFAULT '',
|
|
53
|
+
title TEXT NOT NULL,
|
|
54
|
+
description TEXT,
|
|
55
|
+
acceptance_criteria TEXT,
|
|
56
|
+
assignee TEXT,
|
|
57
|
+
status TEXT NOT NULL DEFAULT 'draft',
|
|
58
|
+
priority TEXT NOT NULL DEFAULT 'medium',
|
|
59
|
+
area TEXT NOT NULL DEFAULT 'FE',
|
|
60
|
+
story_points INTEGER,
|
|
61
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
62
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
63
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
CREATE TABLE IF NOT EXISTS pm_tasks (
|
|
67
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
68
|
+
story_id INTEGER NOT NULL REFERENCES pm_stories(id),
|
|
69
|
+
title TEXT NOT NULL,
|
|
70
|
+
assignee TEXT,
|
|
71
|
+
status TEXT NOT NULL DEFAULT 'todo',
|
|
72
|
+
description TEXT,
|
|
73
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
74
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
75
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
-- ── Page Content ──
|
|
79
|
+
|
|
80
|
+
CREATE TABLE IF NOT EXISTS page_rules (
|
|
81
|
+
id TEXT PRIMARY KEY,
|
|
82
|
+
page_id TEXT NOT NULL,
|
|
83
|
+
rule_group TEXT NOT NULL,
|
|
84
|
+
category TEXT NOT NULL,
|
|
85
|
+
name TEXT NOT NULL,
|
|
86
|
+
condition TEXT NOT NULL DEFAULT '',
|
|
87
|
+
severity TEXT NOT NULL DEFAULT 'info',
|
|
88
|
+
home_message TEXT NOT NULL DEFAULT '',
|
|
89
|
+
action TEXT NOT NULL DEFAULT '',
|
|
90
|
+
data_source TEXT NOT NULL DEFAULT '',
|
|
91
|
+
impl_status TEXT NOT NULL DEFAULT 'new-data',
|
|
92
|
+
impl_note TEXT,
|
|
93
|
+
action_route TEXT,
|
|
94
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
95
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
CREATE TABLE IF NOT EXISTS page_scenarios (
|
|
99
|
+
page_id TEXT NOT NULL,
|
|
100
|
+
sprint TEXT NOT NULL,
|
|
101
|
+
scenario_id TEXT NOT NULL,
|
|
102
|
+
label TEXT NOT NULL,
|
|
103
|
+
data_json TEXT NOT NULL DEFAULT '{}',
|
|
104
|
+
is_default INTEGER NOT NULL DEFAULT 0,
|
|
105
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
106
|
+
author TEXT,
|
|
107
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
108
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
109
|
+
PRIMARY KEY (page_id, sprint, scenario_id)
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
CREATE TABLE IF NOT EXISTS page_spec_areas (
|
|
113
|
+
page_id TEXT NOT NULL,
|
|
114
|
+
area_id TEXT NOT NULL,
|
|
115
|
+
label TEXT NOT NULL,
|
|
116
|
+
short_label TEXT NOT NULL DEFAULT '',
|
|
117
|
+
rule_count INTEGER NOT NULL DEFAULT 0,
|
|
118
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
119
|
+
PRIMARY KEY (page_id, area_id)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
CREATE TABLE IF NOT EXISTS page_versions (
|
|
123
|
+
page_id TEXT NOT NULL,
|
|
124
|
+
sprint TEXT NOT NULL,
|
|
125
|
+
version TEXT NOT NULL DEFAULT '0.1.0',
|
|
126
|
+
last_updated TEXT NOT NULL DEFAULT (datetime('now')),
|
|
127
|
+
status TEXT NOT NULL DEFAULT 'draft',
|
|
128
|
+
changelog TEXT NOT NULL DEFAULT '[]',
|
|
129
|
+
PRIMARY KEY (page_id, sprint)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
CREATE TABLE IF NOT EXISTS wireframe_meta (
|
|
133
|
+
page_id TEXT NOT NULL,
|
|
134
|
+
sprint TEXT NOT NULL,
|
|
135
|
+
default_scenario_id TEXT NOT NULL DEFAULT 'default',
|
|
136
|
+
spec_title TEXT NOT NULL DEFAULT '',
|
|
137
|
+
route_title TEXT NOT NULL DEFAULT '',
|
|
138
|
+
PRIMARY KEY (page_id, sprint)
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
-- ── Scenario Data (custom user scenarios) ──
|
|
142
|
+
|
|
143
|
+
CREATE TABLE IF NOT EXISTS scenario_data (
|
|
144
|
+
page_id TEXT NOT NULL,
|
|
145
|
+
sprint TEXT NOT NULL,
|
|
146
|
+
scenario_id TEXT NOT NULL,
|
|
147
|
+
label TEXT NOT NULL,
|
|
148
|
+
data_json TEXT NOT NULL DEFAULT '{}',
|
|
149
|
+
author TEXT,
|
|
150
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
151
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
152
|
+
PRIMARY KEY (page_id, sprint, scenario_id)
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
-- ── Retro ──
|
|
156
|
+
|
|
157
|
+
CREATE TABLE IF NOT EXISTS retro_sessions (
|
|
158
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
159
|
+
sprint TEXT NOT NULL UNIQUE,
|
|
160
|
+
title TEXT,
|
|
161
|
+
phase TEXT NOT NULL DEFAULT 'write',
|
|
162
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
163
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
CREATE TABLE IF NOT EXISTS retro_items (
|
|
167
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
168
|
+
session_id INTEGER NOT NULL REFERENCES retro_sessions(id),
|
|
169
|
+
category TEXT NOT NULL,
|
|
170
|
+
content TEXT NOT NULL,
|
|
171
|
+
author TEXT NOT NULL,
|
|
172
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
CREATE TABLE IF NOT EXISTS retro_votes (
|
|
176
|
+
item_id INTEGER NOT NULL REFERENCES retro_items(id),
|
|
177
|
+
voter TEXT NOT NULL,
|
|
178
|
+
PRIMARY KEY (item_id, voter)
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
CREATE TABLE IF NOT EXISTS retro_actions (
|
|
182
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
183
|
+
session_id INTEGER NOT NULL REFERENCES retro_sessions(id),
|
|
184
|
+
content TEXT NOT NULL,
|
|
185
|
+
assignee TEXT,
|
|
186
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
187
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
-- ── Memos ──
|
|
191
|
+
|
|
192
|
+
CREATE TABLE IF NOT EXISTS memos (
|
|
193
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
194
|
+
page_id TEXT NOT NULL,
|
|
195
|
+
content TEXT NOT NULL,
|
|
196
|
+
author TEXT NOT NULL DEFAULT '',
|
|
197
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
-- ── Auth ──
|
|
201
|
+
|
|
202
|
+
CREATE TABLE IF NOT EXISTS auth_tokens (
|
|
203
|
+
token TEXT PRIMARY KEY,
|
|
204
|
+
user_name TEXT NOT NULL,
|
|
205
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
206
|
+
expires_at TEXT
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
CREATE TABLE IF NOT EXISTS user_activity (
|
|
210
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
211
|
+
user_name TEXT NOT NULL,
|
|
212
|
+
action TEXT NOT NULL DEFAULT 'login',
|
|
213
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
-- ── Indexes ──
|
|
217
|
+
|
|
218
|
+
CREATE INDEX IF NOT EXISTS idx_nav_epics_sprint ON nav_epics(sprint);
|
|
219
|
+
CREATE INDEX IF NOT EXISTS idx_pm_stories_sprint ON pm_stories(sprint);
|
|
220
|
+
CREATE INDEX IF NOT EXISTS idx_pm_stories_epic ON pm_stories(epic_id);
|
|
221
|
+
CREATE INDEX IF NOT EXISTS idx_pm_tasks_story ON pm_tasks(story_id);
|
|
222
|
+
CREATE INDEX IF NOT EXISTS idx_page_rules_page ON page_rules(page_id);
|
|
223
|
+
CREATE INDEX IF NOT EXISTS idx_retro_items_session ON retro_items(session_id);
|
|
224
|
+
CREATE INDEX IF NOT EXISTS idx_memos_page ON memos(page_id);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic REST API client for spec-site.
|
|
3
|
+
*
|
|
4
|
+
* Provider-agnostic: works with any backend that implements the spec-site
|
|
5
|
+
* API contract (Turso+CF Workers, Supabase, Lambda+RDS, etc.).
|
|
6
|
+
*
|
|
7
|
+
* In static mode (VITE_SPEC_MODE=static), API calls gracefully degrade
|
|
8
|
+
* and composables fall back to local data sources.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const apiUrl = import.meta.env.VITE_API_URL as string | undefined
|
|
12
|
+
const specMode = (import.meta.env.VITE_SPEC_MODE as string) || 'static'
|
|
13
|
+
|
|
14
|
+
let _reachable: boolean | null = null
|
|
15
|
+
|
|
16
|
+
export function isApiMode(): boolean {
|
|
17
|
+
return specMode === 'api' && !!apiUrl
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isStaticMode(): boolean {
|
|
21
|
+
return !isApiMode()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getAuthHeader(): Record<string, string> {
|
|
25
|
+
const token = localStorage.getItem('spec-auth-token')
|
|
26
|
+
if (!token) return {}
|
|
27
|
+
return { Authorization: `Bearer ${token}` }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function apiGet<T = Record<string, unknown>>(
|
|
31
|
+
endpoint: string,
|
|
32
|
+
params?: Record<string, string>,
|
|
33
|
+
): Promise<{ data?: T; error?: string }> {
|
|
34
|
+
if (!apiUrl) return { error: 'Missing API URL' }
|
|
35
|
+
if (_reachable === false) return { error: 'API unreachable' }
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
let url = `${apiUrl}${endpoint}`
|
|
39
|
+
if (params) {
|
|
40
|
+
const qs = new URLSearchParams(params).toString()
|
|
41
|
+
if (qs) url += `?${qs}`
|
|
42
|
+
}
|
|
43
|
+
const resp = await fetch(url, {
|
|
44
|
+
headers: { ...getAuthHeader() },
|
|
45
|
+
signal: AbortSignal.timeout(5000),
|
|
46
|
+
})
|
|
47
|
+
if (!resp.ok) {
|
|
48
|
+
const text = await resp.text().catch(() => '')
|
|
49
|
+
if (resp.status !== 401 && resp.status !== 403 && _reachable === null) _reachable = false
|
|
50
|
+
return { error: `HTTP ${resp.status}: ${text}` }
|
|
51
|
+
}
|
|
52
|
+
_reachable = true
|
|
53
|
+
const data = await resp.json()
|
|
54
|
+
if (data.error) return { error: data.error }
|
|
55
|
+
return { data }
|
|
56
|
+
} catch (err: unknown) {
|
|
57
|
+
if (_reachable === null) {
|
|
58
|
+
_reachable = false
|
|
59
|
+
console.warn('[api] API unreachable, using offline mode')
|
|
60
|
+
}
|
|
61
|
+
return { error: err instanceof Error ? err.message : 'Unknown error' }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function resetReachable() {
|
|
66
|
+
_reachable = null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function apiPost<T = Record<string, unknown>>(
|
|
70
|
+
endpoint: string,
|
|
71
|
+
body: Record<string, unknown>,
|
|
72
|
+
): Promise<{ data?: T; error?: string }> {
|
|
73
|
+
return apiMutate<T>('POST', endpoint, body)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function apiPatch<T = Record<string, unknown>>(
|
|
77
|
+
endpoint: string,
|
|
78
|
+
body: Record<string, unknown>,
|
|
79
|
+
): Promise<{ data?: T; error?: string }> {
|
|
80
|
+
return apiMutate<T>('PATCH', endpoint, body)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function apiPut<T = Record<string, unknown>>(
|
|
84
|
+
endpoint: string,
|
|
85
|
+
body: Record<string, unknown>,
|
|
86
|
+
): Promise<{ data?: T; error?: string }> {
|
|
87
|
+
return apiMutate<T>('PUT', endpoint, body)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function apiDelete<T = Record<string, unknown>>(
|
|
91
|
+
endpoint: string,
|
|
92
|
+
body?: Record<string, unknown>,
|
|
93
|
+
): Promise<{ data?: T; error?: string }> {
|
|
94
|
+
return apiMutate<T>('DELETE', endpoint, body)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function apiMutate<T>(
|
|
98
|
+
method: string,
|
|
99
|
+
endpoint: string,
|
|
100
|
+
body?: Record<string, unknown>,
|
|
101
|
+
): Promise<{ data?: T; error?: string }> {
|
|
102
|
+
if (!apiUrl) return { error: 'Missing API URL' }
|
|
103
|
+
if (_reachable === false) return { error: 'API unreachable' }
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const resp = await fetch(`${apiUrl}${endpoint}`, {
|
|
107
|
+
method,
|
|
108
|
+
headers: {
|
|
109
|
+
'Content-Type': 'application/json',
|
|
110
|
+
...getAuthHeader(),
|
|
111
|
+
},
|
|
112
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
113
|
+
signal: AbortSignal.timeout(5000),
|
|
114
|
+
})
|
|
115
|
+
if (!resp.ok) {
|
|
116
|
+
const text = await resp.text().catch(() => '')
|
|
117
|
+
if (resp.status !== 401 && resp.status !== 403 && _reachable === null) _reachable = false
|
|
118
|
+
return { error: `HTTP ${resp.status}: ${text}` }
|
|
119
|
+
}
|
|
120
|
+
_reachable = true
|
|
121
|
+
const data = await resp.json()
|
|
122
|
+
if (data.error) return { error: data.error }
|
|
123
|
+
return { data }
|
|
124
|
+
} catch (err: unknown) {
|
|
125
|
+
if (_reachable === null) {
|
|
126
|
+
_reachable = false
|
|
127
|
+
console.warn('[api] API unreachable, using offline mode')
|
|
128
|
+
}
|
|
129
|
+
return { error: err instanceof Error ? err.message : 'Unknown error' }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API response types for the spec-site backend contract.
|
|
3
|
+
*
|
|
4
|
+
* Any backend implementation (Turso+CF, Supabase, Lambda+RDS, etc.)
|
|
5
|
+
* must conform to these response shapes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ── Navigation ──
|
|
9
|
+
|
|
10
|
+
export interface NavApiResponse {
|
|
11
|
+
sprints: SprintRow[]
|
|
12
|
+
epics: EpicNavRow[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SprintRow {
|
|
16
|
+
id: string
|
|
17
|
+
label: string
|
|
18
|
+
theme: string
|
|
19
|
+
active: number
|
|
20
|
+
start_date: string | null
|
|
21
|
+
end_date: string | null
|
|
22
|
+
sort_order: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface EpicNavRow {
|
|
26
|
+
sprint: string
|
|
27
|
+
epic_id: string
|
|
28
|
+
label: string
|
|
29
|
+
badge: string | null
|
|
30
|
+
category: string
|
|
31
|
+
description: string | null
|
|
32
|
+
sort_order: number
|
|
33
|
+
uid: string | null
|
|
34
|
+
origin_sprint: string | null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Page Content ──
|
|
38
|
+
|
|
39
|
+
export interface PageContentApiResponse {
|
|
40
|
+
rules: RuleRow[]
|
|
41
|
+
scenarios: ScenarioRow[]
|
|
42
|
+
areas: SpecAreaRow[]
|
|
43
|
+
versions: VersionRow[]
|
|
44
|
+
meta: WireframeMetaRow[]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface RuleRow {
|
|
48
|
+
id: string
|
|
49
|
+
page_id: string
|
|
50
|
+
rule_group: string
|
|
51
|
+
category: string
|
|
52
|
+
name: string
|
|
53
|
+
condition: string
|
|
54
|
+
severity: string
|
|
55
|
+
home_message: string
|
|
56
|
+
action: string
|
|
57
|
+
data_source: string
|
|
58
|
+
impl_status: string
|
|
59
|
+
impl_note: string | null
|
|
60
|
+
action_route: string | null
|
|
61
|
+
sort_order: number
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ScenarioRow {
|
|
65
|
+
scenario_id: string
|
|
66
|
+
label: string
|
|
67
|
+
data_json: string
|
|
68
|
+
is_default: number
|
|
69
|
+
sort_order: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface SpecAreaRow {
|
|
73
|
+
area_id: string
|
|
74
|
+
label: string
|
|
75
|
+
short_label: string
|
|
76
|
+
rule_count: number
|
|
77
|
+
sort_order: number
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface VersionRow {
|
|
81
|
+
page_id: string
|
|
82
|
+
version: string
|
|
83
|
+
last_updated: string
|
|
84
|
+
sprint: string
|
|
85
|
+
status: string
|
|
86
|
+
changelog: string
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface WireframeMetaRow {
|
|
90
|
+
default_scenario_id: string
|
|
91
|
+
spec_title: string
|
|
92
|
+
route_title: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── PM (Project Management) ──
|
|
96
|
+
|
|
97
|
+
export interface PmDataApiResponse {
|
|
98
|
+
stories: PmStoryRow[]
|
|
99
|
+
tasks: PmTaskRow[]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface PmEpicRow {
|
|
103
|
+
id: number
|
|
104
|
+
title: string
|
|
105
|
+
description: string | null
|
|
106
|
+
status: string
|
|
107
|
+
owner: string | null
|
|
108
|
+
created_at: string
|
|
109
|
+
updated_at: string
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface PmStoryRow {
|
|
113
|
+
id: number
|
|
114
|
+
epic_id: number | null
|
|
115
|
+
sprint: string
|
|
116
|
+
epic_uid: string
|
|
117
|
+
title: string
|
|
118
|
+
description: string | null
|
|
119
|
+
acceptance_criteria: string | null
|
|
120
|
+
assignee: string | null
|
|
121
|
+
status: string
|
|
122
|
+
priority: string
|
|
123
|
+
area: string
|
|
124
|
+
story_points: number | null
|
|
125
|
+
sort_order: number
|
|
126
|
+
created_at: string
|
|
127
|
+
updated_at: string
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface PmTaskRow {
|
|
131
|
+
id: number
|
|
132
|
+
story_id: number
|
|
133
|
+
title: string
|
|
134
|
+
assignee: string | null
|
|
135
|
+
status: string
|
|
136
|
+
description: string | null
|
|
137
|
+
sort_order: number
|
|
138
|
+
created_at: string
|
|
139
|
+
updated_at: string
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Auth ──
|
|
143
|
+
|
|
144
|
+
export interface AuthVerifyResponse {
|
|
145
|
+
userName: string
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── Retro ──
|
|
149
|
+
|
|
150
|
+
export interface RetroSessionRow {
|
|
151
|
+
id: number
|
|
152
|
+
sprint: string
|
|
153
|
+
title: string | null
|
|
154
|
+
phase: string
|
|
155
|
+
created_at: string
|
|
156
|
+
updated_at: string
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface RetroItemRow {
|
|
160
|
+
id: number
|
|
161
|
+
session_id: number
|
|
162
|
+
category: string
|
|
163
|
+
content: string
|
|
164
|
+
author: string
|
|
165
|
+
created_at: string
|
|
166
|
+
voteCount: number
|
|
167
|
+
hasVoted: number
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface RetroActionRow {
|
|
171
|
+
id: number
|
|
172
|
+
session_id: number
|
|
173
|
+
content: string
|
|
174
|
+
assignee: string | null
|
|
175
|
+
status: string
|
|
176
|
+
created_at: string
|
|
177
|
+
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
3
3
|
import { useRoute, useRouter } from 'vue-router'
|
|
4
|
-
import { sprints,
|
|
4
|
+
import { sprints, getActiveSprint, type SprintConfig } from '../composables/useNavStore'
|
|
5
|
+
import { featurePages } from '../data/navigation'
|
|
5
6
|
|
|
6
7
|
const route = useRoute()
|
|
7
8
|
const router = useRouter()
|
|
8
9
|
|
|
9
10
|
const currentPageId = computed(() => (route.params.pageId as string) || '')
|
|
10
|
-
const currentSprint = computed(() => (route.params.sprint as string) ||
|
|
11
|
+
const currentSprint = computed(() => (route.params.sprint as string) || getActiveSprint().id)
|
|
11
12
|
|
|
12
13
|
const activeSprintLabel = computed(() => {
|
|
13
|
-
const s = sprints.find(s => s.id === currentSprint.value)
|
|
14
|
+
const s = sprints.value.find(s => s.id === currentSprint.value)
|
|
14
15
|
return s?.label ?? currentSprint.value.toUpperCase()
|
|
15
16
|
})
|
|
16
17
|
|
|
@@ -212,7 +213,7 @@ onUnmounted(() => document.removeEventListener('click', onDocClick))
|
|
|
212
213
|
background: none;
|
|
213
214
|
font-size: 13px;
|
|
214
215
|
font-weight: 600;
|
|
215
|
-
font-family: var(--font-
|
|
216
|
+
font-family: var(--font-sans);
|
|
216
217
|
color: var(--text-primary);
|
|
217
218
|
cursor: pointer;
|
|
218
219
|
transition: all 0.15s;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigation Store — Type definitions and fallback data
|
|
3
|
+
*
|
|
4
|
+
* Fallback data ensures the UI works synchronously before API loads.
|
|
5
|
+
* Replace with your project's sprints/epics or leave empty for API-only mode.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ── Domain types ──
|
|
9
|
+
|
|
10
|
+
export interface SprintConfig {
|
|
11
|
+
id: string
|
|
12
|
+
label: string
|
|
13
|
+
theme: string
|
|
14
|
+
active: boolean
|
|
15
|
+
startDate?: string | null
|
|
16
|
+
endDate?: string | null
|
|
17
|
+
sortOrder: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PageConfig {
|
|
21
|
+
id: string
|
|
22
|
+
label: string
|
|
23
|
+
badge?: string
|
|
24
|
+
category: string
|
|
25
|
+
sprint: string
|
|
26
|
+
description?: string
|
|
27
|
+
sortOrder: number
|
|
28
|
+
uid?: string
|
|
29
|
+
originSprint?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Fallback data (ensures sync access before API loads) ──
|
|
33
|
+
// TODO: Replace with your project's initial sprints and epics
|
|
34
|
+
|
|
35
|
+
export const FALLBACK_SPRINTS: SprintConfig[] = [
|
|
36
|
+
{ id: 's1', label: 'S1', theme: '', active: true, sortOrder: 0 },
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
export const FALLBACK_EPICS: PageConfig[] = []
|