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.
Files changed (81) hide show
  1. package/lib/hydrate.mjs +6 -1
  2. package/lib/setup-wizard.mjs +29 -3
  3. package/package.json +1 -1
  4. package/scaffold/.claude/commands/_domain.md.hbs +33 -0
  5. package/scaffold/.claude/commands/analytics.md.hbs +55 -0
  6. package/scaffold/.claude/commands/daily.md.hbs +301 -0
  7. package/scaffold/.claude/commands/dev.md.hbs +62 -0
  8. package/scaffold/.claude/commands/gtm.md +82 -0
  9. package/scaffold/.claude/commands/handoff.md +259 -0
  10. package/scaffold/.claude/commands/market.md +120 -0
  11. package/scaffold/.claude/commands/metrics.md +123 -0
  12. package/scaffold/.claude/commands/oscar-loop.md +436 -0
  13. package/scaffold/.claude/commands/party.md +85 -0
  14. package/scaffold/.claude/commands/plan.md +43 -0
  15. package/scaffold/.claude/commands/poc.md +69 -0
  16. package/scaffold/.claude/commands/research.md +203 -0
  17. package/scaffold/.claude/commands/retro.md +68 -0
  18. package/scaffold/.claude/commands/save.md +440 -0
  19. package/scaffold/.claude/commands/sessions.md +139 -0
  20. package/scaffold/.claude/commands/sprint.md +106 -0
  21. package/scaffold/.claude/commands/start.md +396 -0
  22. package/scaffold/.claude/commands/strategy.md +41 -0
  23. package/scaffold/.claude/commands/task.md +220 -0
  24. package/scaffold/.claude/commands/tracking.md +116 -0
  25. package/scaffold/.claude/commands/validate.md +58 -0
  26. package/scaffold/.context/WORKFLOW.md.hbs +58 -26
  27. package/scaffold/.context/agents/planner.md.hbs +35 -7
  28. package/scaffold/.context/integrations/_registry.yaml +6 -0
  29. package/scaffold/.context/integrations/providers/sqlite_lambda.yaml +24 -0
  30. package/scaffold/.context/integrations/providers/supabase.yaml +34 -0
  31. package/scaffold/.context/integrations/providers/turso_cf.yaml +34 -0
  32. package/scaffold/.context/poc/_skills/build.md +79 -0
  33. package/scaffold/.context/poc/_skills/scope.md +50 -0
  34. package/scaffold/.context/poc/_skills/spec.md +80 -0
  35. package/scaffold/.context/poc/_skills/verify.md +60 -0
  36. package/scaffold/CLAUDE.md.hbs +210 -0
  37. package/scaffold/spec-site/.env.example +11 -0
  38. package/scaffold/spec-site/index.html +2 -2
  39. package/scaffold/spec-site/sql/schema.sql +224 -0
  40. package/scaffold/spec-site/src/api/client.ts +131 -0
  41. package/scaffold/spec-site/src/api/types.ts +177 -0
  42. package/scaffold/spec-site/src/components/Accordion.vue +1 -1
  43. package/scaffold/spec-site/src/components/AppHeader.vue +5 -4
  44. package/scaffold/spec-site/src/components/CoachingCard.vue +1 -1
  45. package/scaffold/spec-site/src/components/ScenarioSwitcher.vue +1 -1
  46. package/scaffold/spec-site/src/composables/navTypes.ts +39 -0
  47. package/scaffold/spec-site/src/composables/pmTypes.ts +134 -0
  48. package/scaffold/spec-site/src/composables/useAuth.ts +139 -0
  49. package/scaffold/spec-site/src/composables/useMemo.ts +51 -40
  50. package/scaffold/spec-site/src/composables/useNavStore.ts +202 -0
  51. package/scaffold/spec-site/src/composables/usePageContent.ts +208 -0
  52. package/scaffold/spec-site/src/composables/usePmStore.ts +224 -0
  53. package/scaffold/spec-site/src/composables/useRetro.ts +181 -95
  54. package/scaffold/spec-site/src/composables/useScenarioStore.ts +74 -30
  55. package/scaffold/spec-site/src/composables/useUser.ts +12 -6
  56. package/scaffold/spec-site/src/data/navigation.ts +7 -42
  57. package/scaffold/spec-site/src/data/types.ts +13 -43
  58. package/scaffold/spec-site/src/main.ts +7 -0
  59. package/scaffold/spec-site/src/pages/PolicyDetail.vue +30 -11
  60. package/scaffold/spec-site/src/pages/PolicyIndex.vue +22 -7
  61. package/scaffold/spec-site/src/pages/retro/RetroActions.vue +3 -3
  62. package/scaffold/spec-site/src/pages/retro/RetroBoard.vue +2 -2
  63. package/scaffold/spec-site/src/pages/retro/RetroHeader.vue +5 -7
  64. package/scaffold/spec-site/src/pages/retro/RetroPage.vue +2 -2
  65. package/scaffold/spec-site/src/pages/shared/NoContentPlaceholder.vue +2 -2
  66. package/scaffold/spec-site/src/pages/shared/PolicyFallback.vue +25 -13
  67. package/scaffold/spec-site/src/router.ts +11 -7
  68. package/scaffold/spec-site/src/styles/base.css +2 -2
  69. package/scaffold/spec-site/src/styles/split-pane.css +1 -1
  70. package/scaffold/spec-site/src/styles/variables.css +7 -7
  71. package/scaffold/spec-site/src/assets/icons/menu/ic_ads.svg +0 -10
  72. package/scaffold/spec-site/src/assets/icons/menu/ic_ads_on.svg +0 -10
  73. package/scaffold/spec-site/src/assets/icons/menu/ic_board.svg +0 -14
  74. package/scaffold/spec-site/src/assets/icons/menu/ic_board_on.svg +0 -14
  75. package/scaffold/spec-site/src/assets/icons/menu/ic_dashboard.svg +0 -21
  76. package/scaffold/spec-site/src/assets/icons/menu/ic_dashboard_on.svg +0 -21
  77. package/scaffold/spec-site/src/assets/icons/menu/ic_pricing.svg +0 -20
  78. package/scaffold/spec-site/src/assets/icons/menu/ic_pricing_on.svg +0 -20
  79. package/scaffold/spec-site/src/assets/icons/menu/ic_store.svg +0 -11
  80. package/scaffold/spec-site/src/assets/icons/menu/ic_store_on.svg +0 -11
  81. 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
+ }
@@ -58,7 +58,7 @@ function toggle() {
58
58
  background: none;
59
59
  border: none;
60
60
  cursor: pointer;
61
- font-family: var(--font-kr);
61
+ font-family: var(--font-sans);
62
62
  font-size: 15px;
63
63
  font-weight: 600;
64
64
  color: var(--text-primary);
@@ -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, featurePages, type SprintConfig } from '../data/navigation'
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) || sprints[0]?.id || '')
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-kr);
216
+ font-family: var(--font-sans);
216
217
  color: var(--text-primary);
217
218
  cursor: pointer;
218
219
  transition: all 0.15s;
@@ -99,7 +99,7 @@ function toggleCheck() {
99
99
  padding: 7px 16px;
100
100
  border-radius: 6px;
101
101
  font-size: 13px;
102
- font-family: var(--font-kr);
102
+ font-family: var(--font-sans);
103
103
  font-weight: 500;
104
104
  cursor: pointer;
105
105
  border: none;
@@ -76,7 +76,7 @@ function isCustom(id: string) {
76
76
  background: transparent;
77
77
  color: #fff;
78
78
  font-size: 12px;
79
- font-family: var(--font-kr);
79
+ font-family: var(--font-sans);
80
80
  cursor: pointer;
81
81
  transition: all 0.15s;
82
82
  }
@@ -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[] = []