popilot 0.2.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 (136) hide show
  1. package/README.md +372 -0
  2. package/adapters/claude-code/.claude/commands/_domain.md.hbs +32 -0
  3. package/adapters/claude-code/.claude/commands/analytics.md.hbs +55 -0
  4. package/adapters/claude-code/.claude/commands/daily.md.hbs +301 -0
  5. package/adapters/claude-code/.claude/commands/dev.md.hbs +62 -0
  6. package/adapters/claude-code/.claude/commands/handoff.md +258 -0
  7. package/adapters/claude-code/.claude/commands/market.md +120 -0
  8. package/adapters/claude-code/.claude/commands/metrics.md +123 -0
  9. package/adapters/claude-code/.claude/commands/oscar-loop.md +436 -0
  10. package/adapters/claude-code/.claude/commands/party.md +85 -0
  11. package/adapters/claude-code/.claude/commands/plan.md +43 -0
  12. package/adapters/claude-code/.claude/commands/research.md +203 -0
  13. package/adapters/claude-code/.claude/commands/retro.md +68 -0
  14. package/adapters/claude-code/.claude/commands/save.md +440 -0
  15. package/adapters/claude-code/.claude/commands/sessions.md +139 -0
  16. package/adapters/claude-code/.claude/commands/sprint.md +106 -0
  17. package/adapters/claude-code/.claude/commands/start.md +368 -0
  18. package/adapters/claude-code/.claude/commands/strategy.md +41 -0
  19. package/adapters/claude-code/.claude/commands/task.md +220 -0
  20. package/adapters/claude-code/.claude/commands/tracking.md +116 -0
  21. package/adapters/claude-code/.claude/commands/validate.md +58 -0
  22. package/adapters/claude-code/CLAUDE.md.hbs +208 -0
  23. package/adapters/claude-code/manifest.yaml +36 -0
  24. package/bin/cli.mjs +218 -0
  25. package/lib/adapter.mjs +68 -0
  26. package/lib/doctor.mjs +161 -0
  27. package/lib/hydrate.mjs +421 -0
  28. package/lib/prompt.mjs +78 -0
  29. package/lib/scaffold.mjs +155 -0
  30. package/lib/setup-wizard.mjs +331 -0
  31. package/lib/template-engine.mjs +164 -0
  32. package/lib/yaml-lite.mjs +476 -0
  33. package/package.json +30 -0
  34. package/scaffold/.context/.secrets.yaml.example +20 -0
  35. package/scaffold/.context/WORKFLOW.md.hbs +332 -0
  36. package/scaffold/.context/agents/TEMPLATE.md +115 -0
  37. package/scaffold/.context/agents/analyst.md.hbs +362 -0
  38. package/scaffold/.context/agents/developer.md.hbs +390 -0
  39. package/scaffold/.context/agents/handoff-specialist.md.hbs +292 -0
  40. package/scaffold/.context/agents/market-researcher.md.hbs +288 -0
  41. package/scaffold/.context/agents/ollie.md +323 -0
  42. package/scaffold/.context/agents/operations.md.hbs +293 -0
  43. package/scaffold/.context/agents/orchestrator.md.hbs +434 -0
  44. package/scaffold/.context/agents/planner.md.hbs +405 -0
  45. package/scaffold/.context/agents/qa.md.hbs +409 -0
  46. package/scaffold/.context/agents/researcher.md.hbs +330 -0
  47. package/scaffold/.context/agents/sage.md +349 -0
  48. package/scaffold/.context/agents/strategist.md.hbs +339 -0
  49. package/scaffold/.context/agents/tracking-governor.md.hbs +291 -0
  50. package/scaffold/.context/agents/validator.md.hbs +365 -0
  51. package/scaffold/.context/integrations/_registry.yaml +38 -0
  52. package/scaffold/.context/integrations/providers/channel_io.yaml +38 -0
  53. package/scaffold/.context/integrations/providers/corti.yaml +203 -0
  54. package/scaffold/.context/integrations/providers/ga4.yaml +116 -0
  55. package/scaffold/.context/integrations/providers/intercom.yaml +47 -0
  56. package/scaffold/.context/integrations/providers/linear.yaml +46 -0
  57. package/scaffold/.context/integrations/providers/mixpanel.yaml +73 -0
  58. package/scaffold/.context/integrations/providers/notebooklm.yaml +74 -0
  59. package/scaffold/.context/integrations/providers/notion.yaml +129 -0
  60. package/scaffold/.context/integrations/providers/prod_db.yaml +183 -0
  61. package/scaffold/.context/oscar/workflows/multi-agent.md +82 -0
  62. package/scaffold/.context/oscar/workflows/ollie-sage.md +128 -0
  63. package/scaffold/.context/oscar/workflows/session-git.md +71 -0
  64. package/scaffold/.context/oscar/workflows/setup.md +663 -0
  65. package/scaffold/.context/oscar/workflows/tracking.md +118 -0
  66. package/scaffold/.context/project.yaml.example +102 -0
  67. package/scaffold/.context/templates/dev-guide.md +217 -0
  68. package/scaffold/.context/templates/epic-spec.md +225 -0
  69. package/scaffold/.context/templates/guardrail.md +94 -0
  70. package/scaffold/.context/templates/handoff-checklist.md +197 -0
  71. package/scaffold/.context/templates/prd.md +80 -0
  72. package/scaffold/.context/templates/retrospective.md +78 -0
  73. package/scaffold/.context/templates/screen-spec.md +714 -0
  74. package/scaffold/.context/templates/sprint-plan.md +72 -0
  75. package/scaffold/.context/templates/sprint-status.yaml +109 -0
  76. package/scaffold/.context/templates/story-v2.md +228 -0
  77. package/scaffold/.context/templates/validation-report.md +99 -0
  78. package/scaffold/.gitignore.append +7 -0
  79. package/scaffold/spec-site/env.d.ts +7 -0
  80. package/scaffold/spec-site/index.html +14 -0
  81. package/scaffold/spec-site/package.json +20 -0
  82. package/scaffold/spec-site/src/App.vue +27 -0
  83. package/scaffold/spec-site/src/assets/icons/menu/ic_ads.svg +10 -0
  84. package/scaffold/spec-site/src/assets/icons/menu/ic_ads_on.svg +10 -0
  85. package/scaffold/spec-site/src/assets/icons/menu/ic_board.svg +14 -0
  86. package/scaffold/spec-site/src/assets/icons/menu/ic_board_on.svg +14 -0
  87. package/scaffold/spec-site/src/assets/icons/menu/ic_dashboard.svg +21 -0
  88. package/scaffold/spec-site/src/assets/icons/menu/ic_dashboard_on.svg +21 -0
  89. package/scaffold/spec-site/src/assets/icons/menu/ic_pricing.svg +20 -0
  90. package/scaffold/spec-site/src/assets/icons/menu/ic_pricing_on.svg +20 -0
  91. package/scaffold/spec-site/src/assets/icons/menu/ic_store.svg +11 -0
  92. package/scaffold/spec-site/src/assets/icons/menu/ic_store_on.svg +11 -0
  93. package/scaffold/spec-site/src/components/Accordion.vue +108 -0
  94. package/scaffold/spec-site/src/components/AppHeader.vue +304 -0
  95. package/scaffold/spec-site/src/components/Badge.vue +25 -0
  96. package/scaffold/spec-site/src/components/CoachingCard.vue +112 -0
  97. package/scaffold/spec-site/src/components/MemoSidebar.vue +239 -0
  98. package/scaffold/spec-site/src/components/MockupShell.vue +100 -0
  99. package/scaffold/spec-site/src/components/RuleTable.vue +99 -0
  100. package/scaffold/spec-site/src/components/ScenarioSwitcher.vue +103 -0
  101. package/scaffold/spec-site/src/components/SpecNav.vue +26 -0
  102. package/scaffold/spec-site/src/components/SpecSection.vue +59 -0
  103. package/scaffold/spec-site/src/components/SummaryGrid.vue +39 -0
  104. package/scaffold/spec-site/src/components/VersionBadge.vue +38 -0
  105. package/scaffold/spec-site/src/composables/useActiveSection.ts +53 -0
  106. package/scaffold/spec-site/src/composables/useMemo.ts +138 -0
  107. package/scaffold/spec-site/src/composables/useRetro.ts +313 -0
  108. package/scaffold/spec-site/src/composables/useScenario.ts +43 -0
  109. package/scaffold/spec-site/src/composables/useScenarioStore.ts +102 -0
  110. package/scaffold/spec-site/src/composables/useTurso.ts +160 -0
  111. package/scaffold/spec-site/src/composables/useUser.ts +25 -0
  112. package/scaffold/spec-site/src/data/navigation.ts +59 -0
  113. package/scaffold/spec-site/src/data/types.ts +90 -0
  114. package/scaffold/spec-site/src/data/wireframeRegistry.ts +25 -0
  115. package/scaffold/spec-site/src/layouts/SplitPaneLayout.vue +79 -0
  116. package/scaffold/spec-site/src/main.ts +10 -0
  117. package/scaffold/spec-site/src/pages/IndexPage.vue +66 -0
  118. package/scaffold/spec-site/src/pages/PolicyDetail.vue +215 -0
  119. package/scaffold/spec-site/src/pages/PolicyIndex.vue +74 -0
  120. package/scaffold/spec-site/src/pages/retro/RetroActions.vue +191 -0
  121. package/scaffold/spec-site/src/pages/retro/RetroBoard.vue +192 -0
  122. package/scaffold/spec-site/src/pages/retro/RetroCard.vue +131 -0
  123. package/scaffold/spec-site/src/pages/retro/RetroHeader.vue +287 -0
  124. package/scaffold/spec-site/src/pages/retro/RetroPage.vue +178 -0
  125. package/scaffold/spec-site/src/pages/shared/NoContentPlaceholder.vue +34 -0
  126. package/scaffold/spec-site/src/pages/shared/PlaceholderContent.vue +22 -0
  127. package/scaffold/spec-site/src/pages/shared/PlaceholderSpecPanel.vue +16 -0
  128. package/scaffold/spec-site/src/pages/shared/PolicyFallback.vue +145 -0
  129. package/scaffold/spec-site/src/pages/wireframe/WireframeShell.vue +151 -0
  130. package/scaffold/spec-site/src/router.ts +85 -0
  131. package/scaffold/spec-site/src/styles/base.css +21 -0
  132. package/scaffold/spec-site/src/styles/split-pane.css +143 -0
  133. package/scaffold/spec-site/src/styles/variables.css +47 -0
  134. package/scaffold/spec-site/src/utils/markdown.ts +197 -0
  135. package/scaffold/spec-site/tsconfig.json +20 -0
  136. package/scaffold/spec-site/vite.config.ts +18 -0
@@ -0,0 +1,131 @@
1
+ <script setup lang="ts">
2
+ import type { RetroItem, RetroPhase } from '@/composables/useRetro'
3
+
4
+ const props = defineProps<{
5
+ item: RetroItem
6
+ phase: RetroPhase
7
+ currentUser: string
8
+ canVote: boolean
9
+ }>()
10
+
11
+ const emit = defineEmits<{
12
+ (e: 'delete'): void
13
+ (e: 'toggle-vote'): void
14
+ }>()
15
+ </script>
16
+
17
+ <template>
18
+ <div class="retro-card" :class="{ voted: item.hasVoted }">
19
+ <div class="card-content">{{ item.content }}</div>
20
+ <div class="card-footer">
21
+ <span class="card-author">{{ item.author }}</span>
22
+ <div class="card-actions">
23
+ <button
24
+ v-if="phase === 'vote' || phase === 'discuss'"
25
+ class="card-vote-btn"
26
+ :class="{ active: item.hasVoted, disabled: !canVote && !item.hasVoted }"
27
+ :disabled="!canVote && !item.hasVoted"
28
+ @click="emit('toggle-vote')"
29
+ >
30
+ {{ item.hasVoted ? '&#128077;' : '&#9757;' }} {{ item.voteCount }}
31
+ </button>
32
+ <button
33
+ v-if="phase === 'write' && item.author === currentUser"
34
+ class="card-del-btn"
35
+ @click="emit('delete')"
36
+ >
37
+ &times;
38
+ </button>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </template>
43
+
44
+ <style scoped>
45
+ .retro-card {
46
+ background: #fff;
47
+ border: 1px solid rgba(0, 0, 0, 0.06);
48
+ border-radius: 8px;
49
+ padding: 12px;
50
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
51
+ transition: all 0.15s;
52
+ }
53
+ .retro-card.voted {
54
+ border-color: var(--primary);
55
+ background: var(--primary-light);
56
+ }
57
+
58
+ .card-content {
59
+ font-size: 14px;
60
+ line-height: 1.5;
61
+ color: var(--text-primary);
62
+ white-space: pre-wrap;
63
+ word-break: break-word;
64
+ }
65
+
66
+ .card-footer {
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: space-between;
70
+ margin-top: 8px;
71
+ gap: 8px;
72
+ }
73
+
74
+ .card-author {
75
+ font-size: 12px;
76
+ color: var(--text-muted);
77
+ font-weight: 500;
78
+ }
79
+
80
+ .card-actions {
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 4px;
84
+ }
85
+
86
+ .card-vote-btn {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 4px;
90
+ padding: 3px 8px;
91
+ border: 1px solid var(--border);
92
+ border-radius: 12px;
93
+ background: #fff;
94
+ font-size: 12px;
95
+ cursor: pointer;
96
+ transition: all 0.15s;
97
+ color: var(--text-secondary);
98
+ }
99
+ .card-vote-btn:hover:not(:disabled) {
100
+ border-color: var(--primary);
101
+ color: var(--primary);
102
+ }
103
+ .card-vote-btn.active {
104
+ background: var(--primary);
105
+ color: #fff;
106
+ border-color: var(--primary);
107
+ }
108
+ .card-vote-btn.disabled {
109
+ opacity: 0.4;
110
+ cursor: not-allowed;
111
+ }
112
+
113
+ .card-del-btn {
114
+ width: 24px;
115
+ height: 24px;
116
+ border: none;
117
+ background: none;
118
+ color: var(--text-muted);
119
+ font-size: 16px;
120
+ cursor: pointer;
121
+ border-radius: 4px;
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ transition: all 0.15s;
126
+ }
127
+ .card-del-btn:hover {
128
+ background: var(--red-bg);
129
+ color: var(--red);
130
+ }
131
+ </style>
@@ -0,0 +1,287 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import type { RetroSession, RetroPhase } from '@/composables/useRetro'
4
+ import { VOTES_PER_PERSON } from '@/composables/useRetro'
5
+ import type { TeamMember } from '@/composables/useUser'
6
+
7
+ const props = defineProps<{
8
+ session: RetroSession | null
9
+ sprintId: string
10
+ currentUser: string | null
11
+ votesRemaining: number
12
+ teamMembers: string[]
13
+ }>()
14
+
15
+ const emit = defineEmits<{
16
+ (e: 'phase-change', phase: RetroPhase): void
17
+ (e: 'select-user', name: TeamMember): void
18
+ (e: 'reset'): void
19
+ (e: 'export'): void
20
+ }>()
21
+
22
+ const userOpen = ref(false)
23
+
24
+ const PHASE_META: Record<RetroPhase, { label: string; next: string }> = {
25
+ write: { label: 'Writing', next: 'Start Voting' },
26
+ vote: { label: 'Voting', next: 'Start Discussion' },
27
+ discuss: { label: 'Discussing', next: 'Complete' },
28
+ done: { label: 'Done', next: '' },
29
+ }
30
+
31
+ const PHASE_ORDER: RetroPhase[] = ['write', 'vote', 'discuss', 'done']
32
+
33
+ function prevPhase() {
34
+ if (!props.session) return
35
+ const idx = PHASE_ORDER.indexOf(props.session.phase)
36
+ if (idx > 0) emit('phase-change', PHASE_ORDER[idx - 1])
37
+ }
38
+
39
+ function nextPhase() {
40
+ if (!props.session) return
41
+ const idx = PHASE_ORDER.indexOf(props.session.phase)
42
+ if (idx < PHASE_ORDER.length - 1) emit('phase-change', PHASE_ORDER[idx + 1])
43
+ }
44
+
45
+ function phase(): RetroPhase {
46
+ return props.session?.phase ?? 'write'
47
+ }
48
+
49
+ function selectMember(name: string) {
50
+ emit('select-user', name as TeamMember)
51
+ userOpen.value = false
52
+ }
53
+
54
+ const menuOpen = ref(false)
55
+
56
+ function handleReset() {
57
+ menuOpen.value = false
58
+ if (confirm('Reset all retro data for this sprint?')) {
59
+ emit('reset')
60
+ }
61
+ }
62
+
63
+ function handleExport() {
64
+ menuOpen.value = false
65
+ emit('export')
66
+ }
67
+ </script>
68
+
69
+ <template>
70
+ <div class="retro-header">
71
+ <div class="rh-left">
72
+ <span class="rh-sprint">{{ sprintId.toUpperCase() }} Retro</span>
73
+ <span class="rh-phase" :data-phase="phase()">
74
+ {{ PHASE_META[phase()].label }}
75
+ </span>
76
+ <span v-if="phase() === 'vote'" class="rh-votes">
77
+ Votes left: <strong>{{ votesRemaining }}</strong>/{{ VOTES_PER_PERSON }}
78
+ </span>
79
+ </div>
80
+ <div class="rh-right">
81
+ <!-- User selector -->
82
+ <div class="rh-user-wrap">
83
+ <button class="rh-user-btn" @click.stop="userOpen = !userOpen">
84
+ {{ currentUser ?? 'Select name' }}
85
+ <span class="rh-chevron">&#9662;</span>
86
+ </button>
87
+ <div v-if="userOpen" class="rh-user-menu">
88
+ <div
89
+ v-for="m in teamMembers"
90
+ :key="m"
91
+ class="rh-user-item"
92
+ :class="{ active: m === currentUser }"
93
+ @click="selectMember(m)"
94
+ >
95
+ {{ m }}
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- Phase controls -->
101
+ <button v-if="phase() !== 'write'" class="rh-prev" @click="prevPhase">
102
+ &larr; Prev
103
+ </button>
104
+ <button v-if="phase() !== 'done'" class="rh-next" @click="nextPhase">
105
+ {{ PHASE_META[phase()].next }} &rarr;
106
+ </button>
107
+
108
+ <!-- More menu -->
109
+ <div class="rh-menu-wrap">
110
+ <button class="rh-menu-btn" @click.stop="menuOpen = !menuOpen">&#8943;</button>
111
+ <div v-if="menuOpen" class="rh-menu-dropdown">
112
+ <div class="rh-menu-item" @click="handleExport">&#128203; Copy Markdown</div>
113
+ <div class="rh-menu-item rh-menu-danger" @click="handleReset">&#128465; Reset</div>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </template>
119
+
120
+ <style scoped>
121
+ .retro-header {
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: space-between;
125
+ padding: 12px 16px;
126
+ border-bottom: 1px solid var(--border);
127
+ background: var(--card-bg);
128
+ flex-shrink: 0;
129
+ }
130
+
131
+ .rh-left {
132
+ display: flex;
133
+ align-items: center;
134
+ gap: 10px;
135
+ }
136
+
137
+ .rh-sprint {
138
+ font-size: 16px;
139
+ font-weight: 800;
140
+ color: var(--text-primary);
141
+ letter-spacing: -0.3px;
142
+ }
143
+
144
+ .rh-phase {
145
+ font-size: 12px;
146
+ font-weight: 600;
147
+ padding: 3px 10px;
148
+ border-radius: 12px;
149
+ }
150
+ .rh-phase[data-phase='write'] { background: var(--blue-bg); color: var(--blue); }
151
+ .rh-phase[data-phase='vote'] { background: var(--yellow-bg); color: var(--yellow); }
152
+ .rh-phase[data-phase='discuss'] { background: var(--green-bg); color: var(--green); }
153
+ .rh-phase[data-phase='done'] { background: var(--border-light); color: var(--text-muted); }
154
+
155
+ .rh-votes { font-size: 13px; color: var(--text-secondary); }
156
+ .rh-votes strong { color: var(--yellow); }
157
+
158
+ .rh-right {
159
+ display: flex;
160
+ align-items: center;
161
+ gap: 8px;
162
+ }
163
+
164
+ /* -- User dropdown -- */
165
+ .rh-user-wrap { position: relative; }
166
+
167
+ .rh-user-btn {
168
+ display: flex;
169
+ align-items: center;
170
+ gap: 4px;
171
+ padding: 6px 12px;
172
+ border: 1px solid var(--border);
173
+ border-radius: 6px;
174
+ background: #fff;
175
+ font-size: 13px;
176
+ font-weight: 500;
177
+ font-family: var(--font-kr);
178
+ cursor: pointer;
179
+ color: var(--text-primary);
180
+ transition: all 0.15s;
181
+ }
182
+ .rh-user-btn:hover { background: var(--bg); }
183
+
184
+ .rh-chevron {
185
+ font-size: 10px;
186
+ color: var(--text-muted);
187
+ }
188
+
189
+ .rh-user-menu {
190
+ position: absolute;
191
+ top: calc(100% + 4px);
192
+ right: 0;
193
+ min-width: 120px;
194
+ background: #fff;
195
+ border: 1px solid var(--border);
196
+ border-radius: 8px;
197
+ box-shadow: var(--shadow-md);
198
+ padding: 4px;
199
+ z-index: 100;
200
+ }
201
+
202
+ .rh-user-item {
203
+ padding: 6px 12px;
204
+ font-size: 13px;
205
+ border-radius: 6px;
206
+ cursor: pointer;
207
+ color: var(--text-secondary);
208
+ transition: all 0.1s;
209
+ }
210
+ .rh-user-item:hover { background: var(--bg); color: var(--text-primary); }
211
+ .rh-user-item.active { color: var(--primary); font-weight: 600; }
212
+
213
+ /* -- Phase buttons -- */
214
+ .rh-prev {
215
+ padding: 6px 12px;
216
+ background: none;
217
+ color: var(--text-secondary);
218
+ border: 1px solid var(--border);
219
+ border-radius: 6px;
220
+ font-size: 13px;
221
+ font-weight: 500;
222
+ font-family: var(--font-kr);
223
+ cursor: pointer;
224
+ transition: all 0.15s;
225
+ }
226
+ .rh-prev:hover { background: var(--bg); color: var(--text-primary); }
227
+
228
+ .rh-next {
229
+ padding: 6px 14px;
230
+ background: var(--primary);
231
+ color: #fff;
232
+ border: none;
233
+ border-radius: 6px;
234
+ font-size: 13px;
235
+ font-weight: 600;
236
+ font-family: var(--font-kr);
237
+ cursor: pointer;
238
+ transition: opacity 0.15s;
239
+ }
240
+ .rh-next:hover { opacity: 0.9; }
241
+
242
+ /* -- More menu -- */
243
+ .rh-menu-wrap { position: relative; }
244
+
245
+ .rh-menu-btn {
246
+ width: 32px;
247
+ height: 32px;
248
+ display: flex;
249
+ align-items: center;
250
+ justify-content: center;
251
+ background: none;
252
+ border: 1px solid var(--border);
253
+ border-radius: 6px;
254
+ font-size: 18px;
255
+ color: var(--text-secondary);
256
+ cursor: pointer;
257
+ transition: all 0.15s;
258
+ }
259
+ .rh-menu-btn:hover { background: var(--bg); color: var(--text-primary); }
260
+
261
+ .rh-menu-dropdown {
262
+ position: absolute;
263
+ top: calc(100% + 4px);
264
+ right: 0;
265
+ min-width: 150px;
266
+ background: #fff;
267
+ border: 1px solid var(--border);
268
+ border-radius: 8px;
269
+ box-shadow: var(--shadow-md);
270
+ padding: 4px;
271
+ z-index: 100;
272
+ }
273
+
274
+ .rh-menu-item {
275
+ padding: 8px 12px;
276
+ font-size: 13px;
277
+ border-radius: 6px;
278
+ cursor: pointer;
279
+ color: var(--text-secondary);
280
+ transition: all 0.1s;
281
+ white-space: nowrap;
282
+ }
283
+ .rh-menu-item:hover { background: var(--bg); color: var(--text-primary); }
284
+
285
+ .rh-menu-danger { color: var(--red); }
286
+ .rh-menu-danger:hover { background: #FFF4F4; color: var(--red); }
287
+ </style>
@@ -0,0 +1,178 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, watch } from 'vue'
3
+ import { useRoute } from 'vue-router'
4
+ import { useUser } from '@/composables/useUser'
5
+ import { useRetro } from '@/composables/useRetro'
6
+ import { getActiveSprint } from '@/data/navigation'
7
+ import RetroHeader from './RetroHeader.vue'
8
+ import RetroBoard from './RetroBoard.vue'
9
+ import RetroActions from './RetroActions.vue'
10
+
11
+ const route = useRoute()
12
+ const sprintId = (route.params.sprint as string) || getActiveSprint().id
13
+
14
+ const { currentUser, setUser, TEAM_MEMBERS } = useUser()
15
+ const retro = useRetro(sprintId)
16
+
17
+ onMounted(async () => {
18
+ await retro.loadOrCreateSession()
19
+ retro.startPolling(currentUser.value ?? '')
20
+ })
21
+
22
+ watch(currentUser, (nextUser) => {
23
+ retro.startPolling(nextUser ?? '')
24
+ })
25
+
26
+ function handleExport() {
27
+ const md = retro.exportMarkdown()
28
+ if (!md) return
29
+ navigator.clipboard.writeText(md).then(() => {
30
+ alert('Copied to clipboard')
31
+ })
32
+ }
33
+ </script>
34
+
35
+ <template>
36
+ <div class="retro-page">
37
+ <RetroHeader
38
+ :session="retro.session.value"
39
+ :sprint-id="sprintId"
40
+ :current-user="currentUser"
41
+ :votes-remaining="retro.votesRemaining.value"
42
+ :team-members="[...TEAM_MEMBERS]"
43
+ @phase-change="retro.setPhase"
44
+ @select-user="setUser"
45
+ @reset="retro.resetSession"
46
+ @export="handleExport"
47
+ />
48
+
49
+ <div v-if="retro.loading.value" class="retro-loading">
50
+ <div class="loading-spinner" />
51
+ <span>Loading...</span>
52
+ </div>
53
+
54
+ <div v-else-if="retro.error.value" class="retro-error">
55
+ <div class="error-icon">&#9888;</div>
56
+ <div class="error-msg">Connection error: {{ retro.error.value }}</div>
57
+ <button class="error-retry" @click="retro.loadOrCreateSession()">Retry</button>
58
+ </div>
59
+
60
+ <template v-else>
61
+ <RetroBoard
62
+ v-if="retro.session.value && retro.session.value.phase !== 'done'"
63
+ :keep-items="retro.keepItems.value"
64
+ :problem-items="retro.problemItems.value"
65
+ :try-items="retro.tryItems.value"
66
+ :phase="retro.session.value.phase"
67
+ :current-user="currentUser ?? ''"
68
+ :votes-remaining="retro.votesRemaining.value"
69
+ @add-item="retro.addItem"
70
+ @delete-item="(id) => retro.deleteItem(id, currentUser ?? '')"
71
+ @toggle-vote="(id, hasVoted) => retro.toggleVote(id, currentUser ?? '', hasVoted)"
72
+ />
73
+
74
+ <div v-if="retro.session.value?.phase === 'done'" class="retro-done">
75
+ <div class="done-banner">Retro completed</div>
76
+ <RetroBoard
77
+ :keep-items="retro.keepItems.value"
78
+ :problem-items="retro.problemItems.value"
79
+ :try-items="retro.tryItems.value"
80
+ phase="discuss"
81
+ :current-user="currentUser ?? ''"
82
+ :votes-remaining="0"
83
+ @add-item="() => {}"
84
+ @delete-item="() => {}"
85
+ @toggle-vote="() => {}"
86
+ />
87
+ </div>
88
+
89
+ <RetroActions
90
+ v-if="
91
+ retro.session.value?.phase === 'discuss' ||
92
+ retro.session.value?.phase === 'done'
93
+ "
94
+ :actions="retro.actions.value"
95
+ :team-members="[...TEAM_MEMBERS]"
96
+ :readonly="retro.session.value?.phase === 'done'"
97
+ @add-action="retro.addAction"
98
+ @toggle-status="retro.toggleActionStatus"
99
+ />
100
+ </template>
101
+ </div>
102
+ </template>
103
+
104
+ <style scoped>
105
+ .retro-page {
106
+ height: 100%;
107
+ display: flex;
108
+ flex-direction: column;
109
+ overflow: hidden;
110
+ background: var(--bg);
111
+ }
112
+
113
+ /* -- Loading -- */
114
+ .retro-loading {
115
+ flex: 1;
116
+ display: flex;
117
+ flex-direction: column;
118
+ align-items: center;
119
+ justify-content: center;
120
+ gap: 12px;
121
+ color: var(--text-muted);
122
+ font-size: 14px;
123
+ }
124
+
125
+ .loading-spinner {
126
+ width: 28px;
127
+ height: 28px;
128
+ border: 3px solid var(--border);
129
+ border-top-color: var(--primary);
130
+ border-radius: 50%;
131
+ animation: spin 0.8s linear infinite;
132
+ }
133
+
134
+ @keyframes spin {
135
+ to { transform: rotate(360deg); }
136
+ }
137
+
138
+ /* -- Error -- */
139
+ .retro-error {
140
+ flex: 1;
141
+ display: flex;
142
+ flex-direction: column;
143
+ align-items: center;
144
+ justify-content: center;
145
+ gap: 8px;
146
+ }
147
+ .error-icon { font-size: 32px; }
148
+ .error-msg { font-size: 14px; color: var(--red); }
149
+ .error-retry {
150
+ margin-top: 8px;
151
+ padding: 8px 20px;
152
+ background: var(--primary);
153
+ color: #fff;
154
+ border: none;
155
+ border-radius: 6px;
156
+ font-size: 13px;
157
+ font-weight: 600;
158
+ font-family: var(--font-kr);
159
+ cursor: pointer;
160
+ }
161
+
162
+ /* -- Done -- */
163
+ .retro-done {
164
+ flex: 1;
165
+ display: flex;
166
+ flex-direction: column;
167
+ min-height: 0;
168
+ }
169
+ .done-banner {
170
+ text-align: center;
171
+ padding: 10px;
172
+ background: var(--green-bg);
173
+ color: var(--green);
174
+ font-weight: 700;
175
+ font-size: 14px;
176
+ border-bottom: 1px solid var(--green-border);
177
+ }
178
+ </style>
@@ -0,0 +1,34 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { sprints } from '@/data/navigation'
4
+
5
+ const props = defineProps<{
6
+ pageId: string
7
+ sprint: string
8
+ }>()
9
+
10
+ const sprintLabel = computed(() => sprints.find(s => s.id === props.sprint)?.label ?? props.sprint)
11
+ </script>
12
+
13
+ <template>
14
+ <div class="no-content">
15
+ <div class="no-content-icon">📭</div>
16
+ <h2>No spec available</h2>
17
+ <p>No spec has been created for this feature in {{ sprintLabel }}.</p>
18
+ <div class="no-content-badge">Sprint not available</div>
19
+ </div>
20
+ </template>
21
+
22
+ <style scoped>
23
+ .no-content {
24
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
25
+ height: calc(100vh - var(--header-height)); color: var(--text-muted); text-align: center; padding: 40px;
26
+ }
27
+ .no-content-icon { font-size: 64px; margin-bottom: 16px; opacity: 0.3; }
28
+ h2 { font-size: 24px; color: var(--text-primary); margin-bottom: 8px; }
29
+ p { font-size: 14px; margin-bottom: 24px; }
30
+ .no-content-badge {
31
+ padding: 8px 16px; border-radius: 6px;
32
+ background: var(--border-light); color: var(--text-muted); font-size: 12px; font-weight: 600;
33
+ }
34
+ </style>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <div class="placeholder-content">
3
+ <div class="placeholder-icon">📝</div>
4
+ <h2>Spec coming soon</h2>
5
+ <p>Interactive mockup for this screen will be added soon.</p>
6
+ <div class="placeholder-badge">In progress</div>
7
+ </div>
8
+ </template>
9
+
10
+ <style scoped>
11
+ .placeholder-content {
12
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
13
+ height: 100%; color: var(--text-muted); text-align: center; padding: 40px;
14
+ }
15
+ .placeholder-icon { font-size: 64px; margin-bottom: 16px; opacity: 0.3; }
16
+ h2 { font-size: 24px; color: var(--text-primary); margin-bottom: 8px; }
17
+ p { font-size: 14px; margin-bottom: 24px; }
18
+ .placeholder-badge {
19
+ padding: 8px 16px; border-radius: 6px;
20
+ background: var(--yellow-bg); color: var(--yellow); font-size: 12px; font-weight: 600;
21
+ }
22
+ </style>
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <div class="spec-placeholder">
3
+ <div class="placeholder-icon">👈</div>
4
+ When the mockup is ready,<br>
5
+ specs will appear here.
6
+ </div>
7
+ </template>
8
+
9
+ <style scoped>
10
+ .spec-placeholder {
11
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
12
+ height: 200px; color: var(--text-muted); text-align: center;
13
+ font-size: 14px; line-height: 1.6;
14
+ }
15
+ .placeholder-icon { font-size: 32px; margin-bottom: 12px; opacity: 0.3; }
16
+ </style>