popilot 0.7.0 → 0.8.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 (56) hide show
  1. package/package.json +1 -1
  2. package/scaffold/mcp-notification-server/package.json +18 -0
  3. package/scaffold/mcp-notification-server/src/index.ts +275 -0
  4. package/scaffold/mcp-notification-server/src/turso-client.ts +142 -0
  5. package/scaffold/mcp-notification-server/tsconfig.json +14 -0
  6. package/scaffold/pm-api/sql/001-memo-v2.sql +49 -0
  7. package/scaffold/pm-api/sql/002-notifications.sql +18 -0
  8. package/scaffold/pm-api/sql/003-content.sql +66 -0
  9. package/scaffold/pm-api/sql/004-agent-events.sql +21 -0
  10. package/scaffold/pm-api/sql/005-epic-sprint-decoupling.sql +6 -0
  11. package/scaffold/pm-api/src/utils/retro-link.ts +32 -0
  12. package/scaffold/spec-site/package-lock.json +852 -0
  13. package/scaffold/spec-site/package.json +12 -1
  14. package/scaffold/spec-site/src/components/AuthGate.vue +117 -0
  15. package/scaffold/spec-site/src/components/BurndownChart.vue +78 -0
  16. package/scaffold/spec-site/src/components/DocComments.vue +137 -0
  17. package/scaffold/spec-site/src/components/DocEditor.vue +118 -0
  18. package/scaffold/spec-site/src/components/DocExportBar.vue +110 -0
  19. package/scaffold/spec-site/src/components/DocsSidebar.vue +309 -0
  20. package/scaffold/spec-site/src/components/EmptyState.vue +30 -0
  21. package/scaffold/spec-site/src/components/ErrorBanner.vue +38 -0
  22. package/scaffold/spec-site/src/components/Icon.vue +58 -0
  23. package/scaffold/spec-site/src/components/MemoChecklist.vue +88 -0
  24. package/scaffold/spec-site/src/components/MemoGraph.vue +75 -0
  25. package/scaffold/spec-site/src/components/MemoItem.vue +353 -0
  26. package/scaffold/spec-site/src/components/MemoRelations.vue +101 -0
  27. package/scaffold/spec-site/src/components/MemoTimeline.vue +53 -0
  28. package/scaffold/spec-site/src/components/MentionInput.vue +174 -0
  29. package/scaffold/spec-site/src/components/PriorityBadge.vue +23 -0
  30. package/scaffold/spec-site/src/components/SlashCommand.ts +123 -0
  31. package/scaffold/spec-site/src/components/StateDisplay.vue +54 -0
  32. package/scaffold/spec-site/src/components/TreeNode.vue +82 -0
  33. package/scaffold/spec-site/src/components/UserAvatar.vue +24 -0
  34. package/scaffold/spec-site/src/composables/navTypes.ts +3 -0
  35. package/scaffold/spec-site/src/composables/useBottomSheet.ts +103 -0
  36. package/scaffold/spec-site/src/composables/useMemo.ts +39 -0
  37. package/scaffold/spec-site/src/composables/useTurso.ts +17 -0
  38. package/scaffold/spec-site/src/composables/useViewport.ts +26 -0
  39. package/scaffold/spec-site/src/mockup/ComponentPalette.vue +61 -0
  40. package/scaffold/spec-site/src/mockup/MockupCanvas.vue +459 -0
  41. package/scaffold/spec-site/src/mockup/PropertyPanel.vue +217 -0
  42. package/scaffold/spec-site/src/mockup/componentCatalog.ts +68 -0
  43. package/scaffold/spec-site/src/mockup/useScenarios.ts +67 -0
  44. package/scaffold/spec-site/src/pages/DocsEditor.vue +119 -0
  45. package/scaffold/spec-site/src/pages/DocsPage.vue +444 -0
  46. package/scaffold/spec-site/src/pages/MemosPage.vue +857 -0
  47. package/scaffold/spec-site/src/pages/MockupEditorPage.vue +611 -0
  48. package/scaffold/spec-site/src/pages/MockupListPage.vue +121 -0
  49. package/scaffold/spec-site/src/pages/MockupViewerPage.vue +199 -0
  50. package/scaffold/spec-site/src/pages/NotificationSettingsPage.vue +59 -0
  51. package/scaffold/spec-site/src/pages/SprintAdmin.vue +521 -0
  52. package/scaffold/spec-site/src/pages/SprintTimeline.vue +159 -0
  53. package/scaffold/spec-site/src/pages/board/KanbanBoard.vue +93 -0
  54. package/scaffold/spec-site/src/styles/buttons.css +124 -0
  55. package/scaffold/spec-site/src/utils/parseMentions.ts +56 -0
  56. package/scaffold/spec-site/src/utils/timezone.ts +18 -0
@@ -0,0 +1,93 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { apiPatch } from '@/composables/useTurso'
4
+
5
+ interface Story { id: number; title: string; status: string; priority: string; assignee: string; story_points: number; epic_id: number; epic_uid?: string }
6
+
7
+ const props = defineProps<{ stories: Story[]; onUpdate?: () => void }>()
8
+
9
+ const COLUMNS = [
10
+ { id: 'backlog', label: 'Backlog', color: '#9ca3af' },
11
+ { id: 'ready-for-dev', label: 'Ready', color: '#3b82f6' },
12
+ { id: 'in-progress', label: 'In Progress', color: '#f59e0b' },
13
+ { id: 'review', label: 'Review', color: '#8b5cf6' },
14
+ { id: 'done', label: 'Done', color: '#22c55e' },
15
+ ]
16
+
17
+ const grouped = computed(() => {
18
+ const groups: Record<string, Story[]> = {}
19
+ for (const col of COLUMNS) groups[col.id] = []
20
+ for (const s of props.stories) {
21
+ const col = COLUMNS.find(c => c.id === s.status) ? s.status : 'backlog'
22
+ groups[col].push(s)
23
+ }
24
+ return groups
25
+ })
26
+
27
+ let dragStoryId: number | null = null
28
+
29
+ function onDragStart(e: DragEvent, storyId: number) {
30
+ dragStoryId = storyId
31
+ e.dataTransfer?.setData('story-id', String(storyId))
32
+ }
33
+
34
+ async function onDrop(e: DragEvent, newStatus: string) {
35
+ e.preventDefault()
36
+ const id = Number(e.dataTransfer?.getData('story-id') || dragStoryId)
37
+ if (!id) return
38
+ await apiPatch(`/api/v2/pm/stories/${id}`, { status: newStatus })
39
+ const story = props.stories.find(s => s.id === id)
40
+ if (story) story.status = newStatus
41
+ props.onUpdate?.()
42
+ dragStoryId = null
43
+ }
44
+
45
+ const priorityColors: Record<string, string> = { high: '#ef4444', medium: '#f59e0b', low: '#22c55e' }
46
+ </script>
47
+
48
+ <template>
49
+ <div class="kanban-board">
50
+ <div v-for="col in COLUMNS" :key="col.id" class="kanban-column" @dragover.prevent @drop="onDrop($event, col.id)">
51
+ <div class="column-header" :style="{ borderTopColor: col.color }">
52
+ <span class="column-title">{{ col.label }}</span>
53
+ <span class="column-count">{{ grouped[col.id].length }}</span>
54
+ </div>
55
+ <div class="column-body">
56
+ <div
57
+ v-for="story in grouped[col.id]" :key="story.id"
58
+ class="kanban-card"
59
+ draggable="true"
60
+ @dragstart="onDragStart($event, story.id)"
61
+ >
62
+ <div class="card-top">
63
+ <span class="card-id">SID:{{ story.id }}</span>
64
+ <span class="card-sp" v-if="story.story_points">{{ story.story_points }}SP</span>
65
+ </div>
66
+ <div class="card-title">{{ story.title }}</div>
67
+ <div class="card-bottom">
68
+ <span class="card-priority" :style="{ color: priorityColors[story.priority] || '#9ca3af' }">{{ story.priority }}</span>
69
+ <span v-if="story.assignee" class="card-assignee">{{ story.assignee }}</span>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </template>
76
+
77
+ <style scoped>
78
+ .kanban-board { display: flex; gap: 12px; overflow-x: auto; padding: 8px 0; min-height: 400px; }
79
+ .kanban-column { flex: 1; min-width: 200px; background: var(--bg-card, #fff); border-radius: var(--radius-lg, 12px); display: flex; flex-direction: column; }
80
+ .column-header { padding: 12px 14px; border-top: 3px solid; display: flex; justify-content: space-between; align-items: center; border-radius: var(--radius-lg, 12px) var(--radius-lg, 12px) 0 0; }
81
+ .column-title { font-size: 13px; font-weight: 700; color: var(--text-primary); }
82
+ .column-count { font-size: 11px; background: var(--bg-hover); padding: 1px 6px; border-radius: 8px; color: var(--text-secondary); }
83
+ .column-body { padding: 8px; flex: 1; display: flex; flex-direction: column; gap: 6px; min-height: 100px; }
84
+ .kanban-card { background: var(--bg-main, #f5f6f8); border-radius: var(--radius-md, 8px); padding: 10px 12px; cursor: grab; transition: transform 0.1s; }
85
+ .kanban-card:active { cursor: grabbing; opacity: 0.7; }
86
+ .kanban-card:hover { transform: translateY(-1px); }
87
+ .card-top { display: flex; justify-content: space-between; font-size: 11px; color: var(--text-muted); margin-bottom: 4px; }
88
+ .card-title { font-size: 13px; font-weight: 600; color: var(--text-primary); line-height: 1.4; }
89
+ .card-bottom { display: flex; justify-content: space-between; font-size: 11px; margin-top: 6px; }
90
+ .card-priority { font-weight: 600; }
91
+ .card-assignee { color: var(--text-secondary); }
92
+ .card-sp { font-weight: 600; }
93
+ </style>
@@ -0,0 +1,124 @@
1
+ /* Button design system */
2
+
3
+ .btn {
4
+ display: inline-flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ gap: 6px;
8
+ border: none;
9
+ border-radius: var(--radius-md, 8px);
10
+ font-size: 13px;
11
+ font-weight: 500;
12
+ cursor: pointer;
13
+ transition: all 0.15s ease;
14
+ padding: 8px 16px;
15
+ line-height: 1.4;
16
+ white-space: nowrap;
17
+ font-family: inherit;
18
+ }
19
+
20
+ .btn:disabled {
21
+ opacity: 0.5;
22
+ cursor: not-allowed;
23
+ }
24
+
25
+ /* Primary */
26
+ .btn--primary {
27
+ background: #3b82f6;
28
+ color: #fff;
29
+ }
30
+ .btn--primary:hover:not(:disabled) {
31
+ background: #2563eb;
32
+ }
33
+
34
+ /* Secondary */
35
+ .btn--secondary {
36
+ background: #f3f4f6;
37
+ color: #374151;
38
+ }
39
+ .btn--secondary:hover:not(:disabled) {
40
+ background: #e5e7eb;
41
+ }
42
+
43
+ /* Ghost */
44
+ .btn--ghost {
45
+ background: transparent;
46
+ color: #6b7280;
47
+ }
48
+ .btn--ghost:hover:not(:disabled) {
49
+ background: #f3f4f6;
50
+ color: #374151;
51
+ }
52
+
53
+ /* Danger */
54
+ .btn--danger {
55
+ background: #fef2f2;
56
+ color: #dc2626;
57
+ }
58
+ .btn--danger:hover:not(:disabled) {
59
+ background: #fee2e2;
60
+ }
61
+
62
+ /* Sizes */
63
+ .btn--xs {
64
+ font-size: 11px;
65
+ padding: 4px 8px;
66
+ border-radius: 6px;
67
+ }
68
+ .btn--sm {
69
+ font-size: 12px;
70
+ padding: 6px 12px;
71
+ }
72
+ .btn--lg {
73
+ font-size: 15px;
74
+ padding: 10px 20px;
75
+ }
76
+
77
+ /* Icon button */
78
+ .btn--icon {
79
+ width: 32px;
80
+ height: 32px;
81
+ padding: 0;
82
+ border-radius: 8px;
83
+ }
84
+ .btn--icon.btn--xs {
85
+ width: 24px;
86
+ height: 24px;
87
+ }
88
+
89
+ /* Input/Select globals */
90
+ .input, .select,
91
+ input[type="text"], input[type="search"], input[type="email"], input[type="number"], input[type="date"],
92
+ select, textarea {
93
+ font-family: inherit;
94
+ font-size: 13px;
95
+ border: 1px solid #e2e8f0;
96
+ border-radius: var(--radius-md, 8px);
97
+ padding: 8px 12px;
98
+ background: var(--bg-input, #f5f5f5);
99
+ color: var(--text-primary, #1a1a1a);
100
+ transition: border-color 0.15s, box-shadow 0.15s;
101
+ outline: none;
102
+ }
103
+ input:focus, select:focus, textarea:focus {
104
+ border-color: var(--primary);
105
+ box-shadow: 0 0 0 3px var(--primary-light, rgba(59,130,246,0.12));
106
+ background: var(--bg-card, #fff);
107
+ }
108
+ input::placeholder, textarea::placeholder {
109
+ color: var(--text-muted, #9ca3af);
110
+ }
111
+
112
+ /* Badge */
113
+ .badge {
114
+ display: inline-flex;
115
+ align-items: center;
116
+ padding: 2px 8px;
117
+ border-radius: 10px;
118
+ font-size: 11px;
119
+ font-weight: 600;
120
+ }
121
+ .badge--success { background: #dcfce7; color: #16a34a; }
122
+ .badge--warning { background: #fef3c7; color: #d97706; }
123
+ .badge--danger { background: #fee2e2; color: #dc2626; }
124
+ .badge--info { background: #dbeafe; color: #1d4ed8; }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @pageName mentions → clickable links
3
+ * XSS-safe: text is escaped first, then only known page names are linked.
4
+ *
5
+ * Page mentions are empty by default — register your project's pages
6
+ * by editing the PAGE_MENTIONS array below.
7
+ */
8
+
9
+ const PAGE_MENTIONS: { label: string; path: string }[] = [
10
+ // TODO: Add your project's page mentions
11
+ // { label: 'Home', path: '/home' },
12
+ // { label: 'Board', path: '/board' },
13
+ // { label: 'Standup', path: '/standup' },
14
+ // { label: 'Retro', path: '/retro' },
15
+ ]
16
+
17
+ // Match longest labels first
18
+ const SORTED_LABELS = [...PAGE_MENTIONS].sort((a, b) => b.label.length - a.label.length)
19
+
20
+ function escapeHtml(text: string): string {
21
+ return text
22
+ .replace(/&/g, '&amp;')
23
+ .replace(/</g, '&lt;')
24
+ .replace(/>/g, '&gt;')
25
+ .replace(/"/g, '&quot;')
26
+ }
27
+
28
+ /**
29
+ * Convert @pageName and @person mentions to clickable HTML.
30
+ * Returns an HTML string (use with v-html).
31
+ */
32
+ export function parseMentions(text: string): string {
33
+ let html = escapeHtml(text)
34
+
35
+ for (const { label, path } of SORTED_LABELS) {
36
+ const escaped = escapeHtml(label)
37
+ const regex = new RegExp(`@${escaped}`, 'g')
38
+ html = html.replace(
39
+ regex,
40
+ `<a href="${path}" class="memo-mention" data-mention-page="${path}">@${escaped}</a>`,
41
+ )
42
+ }
43
+
44
+ // Person mentions — unmatched @name rendered as blue chip
45
+ html = html.replace(/@([^@\s&lt;][^@\n]*?)(?=\s|$|&lt;|@)/g, (match, name) => {
46
+ if (match.includes('class="memo-mention"')) return match
47
+ return `<span class="mention-chip">@${name}</span>`
48
+ })
49
+
50
+ return html
51
+ }
52
+
53
+ /** Check if text contains any page mentions */
54
+ export function hasMentions(text: string): boolean {
55
+ return SORTED_LABELS.some(({ label }) => text.includes(`@${label}`))
56
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Timezone-aware date/time helpers.
3
+ * Default timezone can be configured via VITE_TIMEZONE env var.
4
+ */
5
+
6
+ const DEFAULT_TZ = (import.meta.env.VITE_TIMEZONE as string) || Intl.DateTimeFormat().resolvedOptions().timeZone
7
+
8
+ /** YYYY-MM-DD in the configured timezone */
9
+ export function toDateString(date: Date = new Date(), tz: string = DEFAULT_TZ): string {
10
+ return date.toLocaleDateString('en-CA', { timeZone: tz })
11
+ }
12
+
13
+ /** YYYY-MM-DD HH:mm in the configured timezone */
14
+ export function toDateTimeString(date: Date = new Date(), tz: string = DEFAULT_TZ): string {
15
+ const d = date.toLocaleDateString('en-CA', { timeZone: tz })
16
+ const t = date.toLocaleTimeString('en-GB', { timeZone: tz, hour: '2-digit', minute: '2-digit', hour12: false })
17
+ return `${d} ${t}`
18
+ }