Skip to content

Commit 999a818

Browse files
committed
docs(createRegistry): add task manager example and sequence diagram styling
Multi-file example (context.ts, TaskProvider.vue, TaskConsumer.vue, task-manager.vue) demonstrating the full registry lifecycle with events, onboard, upsert, and offboard. Adds DocsMermaid sequence diagram CSS and updates architecture diagram to include the new createQueue entry. File breakdown table updated to reference RegistryTicketInput.
1 parent a178bd7 commit 999a818

File tree

6 files changed

+413
-6
lines changed

6 files changed

+413
-6
lines changed

apps/docs/src/components/docs/DocsMermaid.vue

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,76 @@
509509
opacity: 1 !important;
510510
}
511511
512+
/* Sequence diagram styling */
513+
.docs-mermaid rect.actor {
514+
rx: 8px;
515+
ry: 8px;
516+
fill: var(--v0-surface) !important;
517+
stroke: var(--v0-divider) !important;
518+
}
519+
520+
.docs-mermaid text.actor > tspan {
521+
fill: var(--v0-on-surface) !important;
522+
}
523+
524+
.docs-mermaid .actor-line {
525+
stroke: var(--v0-divider) !important;
526+
}
527+
528+
.docs-mermaid .messageLine0,
529+
.docs-mermaid .messageLine1 {
530+
stroke: var(--v0-primary) !important;
531+
}
532+
533+
.docs-mermaid .messageText {
534+
fill: var(--v0-on-surface) !important;
535+
stroke: none !important;
536+
}
537+
538+
.docs-mermaid .activation {
539+
fill: color-mix(in srgb, var(--v0-primary) 10%, transparent) !important;
540+
stroke: var(--v0-primary) !important;
541+
}
542+
543+
.docs-mermaid .labelBox {
544+
fill: var(--v0-surface-variant) !important;
545+
stroke: var(--v0-divider) !important;
546+
}
547+
548+
.docs-mermaid .labelText,
549+
.docs-mermaid .labelText > tspan {
550+
fill: var(--v0-on-surface-variant) !important;
551+
}
552+
553+
.docs-mermaid .loopLine {
554+
stroke: var(--v0-divider) !important;
555+
fill: none !important;
556+
}
557+
558+
.docs-mermaid .loopText,
559+
.docs-mermaid .loopText > tspan {
560+
fill: var(--v0-on-surface-variant) !important;
561+
}
562+
563+
.docs-mermaid .note {
564+
fill: var(--v0-surface-variant) !important;
565+
stroke: var(--v0-divider) !important;
566+
}
567+
568+
.docs-mermaid .noteText,
569+
.docs-mermaid .noteText > tspan {
570+
fill: var(--v0-on-surface) !important;
571+
}
572+
573+
.docs-mermaid #arrowhead path {
574+
fill: var(--v0-primary) !important;
575+
stroke: var(--v0-primary) !important;
576+
}
577+
578+
.docs-mermaid .sequenceNumber {
579+
fill: var(--v0-on-primary) !important;
580+
}
581+
512582
/* Semantic node classes (use with classDef in mermaid) */
513583
.docs-mermaid .node.primary rect,
514584
.docs-mermaid .node.primary polygon {
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<script setup lang="ts">
2+
import AppIcon from '@/components/app/AppIcon.vue'
3+
import { computed, shallowRef } from 'vue'
4+
5+
import { useTaskRegistry, type TaskTicket } from './context'
6+
7+
const { tasks, eventLog, addTask, removeTask, toggleDone, clearCompleted } = useTaskRegistry()
8+
9+
const newTask = shallowRef('')
10+
const newPriority = shallowRef<TaskTicket['priority']>('medium')
11+
const filterPriority = shallowRef<TaskTicket['priority'] | 'all'>('all')
12+
13+
const filteredTasks = computed(() => {
14+
if (filterPriority.value === 'all') return tasks.value
15+
return tasks.value.filter(t => t.priority === filterPriority.value)
16+
})
17+
18+
const stats = computed(() => ({
19+
total: tasks.value.length,
20+
done: tasks.value.filter(t => t.done).length,
21+
high: tasks.value.filter(t => t.priority === 'high' && !t.done).length,
22+
}))
23+
24+
function handleAdd () {
25+
const text = newTask.value.trim()
26+
if (!text) return
27+
addTask(text, newPriority.value)
28+
newTask.value = ''
29+
}
30+
31+
const priorityStyles: Record<string, { dot: string, badge: string }> = {
32+
high: { dot: 'bg-error', badge: 'bg-error/10 text-error' },
33+
medium: { dot: 'bg-warning', badge: 'bg-warning/10 text-warning' },
34+
low: { dot: 'bg-primary', badge: 'bg-primary/10 text-primary' },
35+
}
36+
</script>
37+
38+
<template>
39+
<div class="space-y-4">
40+
<!-- Stats bar -->
41+
<div class="flex items-center gap-4 text-xs text-on-surface-variant">
42+
<span>{{ stats.total }} tasks</span>
43+
<span class="text-success">{{ stats.done }} done</span>
44+
<span v-if="stats.high > 0" class="text-error">{{ stats.high }} high priority</span>
45+
<span class="ml-auto font-mono text-on-surface-variant/60">
46+
registry.size = {{ tasks.length }}
47+
</span>
48+
</div>
49+
50+
<!-- Add task -->
51+
<div class="flex gap-2">
52+
<select
53+
v-model="newPriority"
54+
class="px-2 py-1.5 text-xs rounded-lg border border-divider bg-surface text-on-surface"
55+
>
56+
<option value="high">High</option>
57+
<option value="medium">Medium</option>
58+
<option value="low">Low</option>
59+
</select>
60+
<input
61+
v-model="newTask"
62+
class="flex-1 px-3 py-1.5 text-sm rounded-lg border border-divider bg-surface text-on-surface placeholder:text-on-surface-variant outline-none focus:border-primary"
63+
placeholder="Add a task..."
64+
@keydown.enter="handleAdd"
65+
>
66+
<button
67+
class="px-3 py-1.5 text-sm rounded-lg bg-primary text-on-primary hover:bg-primary/90 disabled:opacity-40 transition-colors"
68+
:disabled="!newTask.trim()"
69+
@click="handleAdd"
70+
>
71+
Add
72+
</button>
73+
</div>
74+
75+
<!-- Filter -->
76+
<div class="flex items-center gap-1.5">
77+
<button
78+
v-for="f in (['all', 'high', 'medium', 'low'] as const)"
79+
:key="f"
80+
class="px-2 py-0.5 text-xs rounded-md border transition-all"
81+
:class="filterPriority === f
82+
? 'border-primary bg-primary/10 text-primary font-medium'
83+
: 'border-divider text-on-surface-variant hover:border-primary/50'"
84+
@click="filterPriority = f"
85+
>
86+
{{ f === 'all' ? 'All' : f.charAt(0).toUpperCase() + f.slice(1) }}
87+
</button>
88+
<button
89+
v-if="stats.done > 0"
90+
class="ml-auto text-xs text-on-surface-variant hover:text-error transition-colors"
91+
@click="clearCompleted"
92+
>
93+
Clear {{ stats.done }} completed
94+
</button>
95+
</div>
96+
97+
<!-- Task list -->
98+
<div class="space-y-1">
99+
<div
100+
v-for="task in filteredTasks"
101+
:key="task.id"
102+
class="group flex items-center gap-3 px-3 py-2 rounded-lg border transition-all"
103+
:class="task.done
104+
? 'border-divider/50 bg-surface-variant/20'
105+
: 'border-divider hover:border-primary/30'"
106+
>
107+
<button
108+
class="size-4.5 rounded border-2 flex items-center justify-center transition-all"
109+
:class="task.done
110+
? 'border-primary bg-primary'
111+
: 'border-divider hover:border-primary'"
112+
@click="toggleDone(task.id)"
113+
>
114+
<AppIcon v-if="task.done" class="text-on-primary" icon="check" :size="12" />
115+
</button>
116+
117+
<span
118+
class="size-2 rounded-full flex-shrink-0"
119+
:class="priorityStyles[task.priority].dot"
120+
/>
121+
122+
<span
123+
class="flex-1 text-sm transition-all"
124+
:class="task.done ? 'line-through text-on-surface-variant/60' : 'text-on-surface'"
125+
>
126+
{{ task.value }}
127+
</span>
128+
129+
<span
130+
class="px-1.5 py-0.5 text-[10px] rounded font-medium"
131+
:class="priorityStyles[task.priority].badge"
132+
>
133+
{{ task.priority }}
134+
</span>
135+
136+
<span class="text-[10px] font-mono text-on-surface-variant/40">#{{ task.index }}</span>
137+
138+
<button
139+
class="opacity-0 group-hover:opacity-100 text-on-surface-variant hover:text-error transition-all"
140+
@click="removeTask(task.id)"
141+
>
142+
<AppIcon icon="close" :size="14" />
143+
</button>
144+
</div>
145+
146+
<p
147+
v-if="filteredTasks.length === 0"
148+
class="text-center text-sm text-on-surface-variant py-4"
149+
>
150+
{{ filterPriority === 'all' ? 'No tasks yet.' : `No ${filterPriority} priority tasks.` }}
151+
</p>
152+
</div>
153+
154+
<!-- Event log -->
155+
<div class="rounded-lg bg-surface-variant/30 p-3">
156+
<p class="text-[10px] uppercase tracking-wider text-on-surface-variant/60 mb-1.5">Event log</p>
157+
<div class="space-y-0.5">
158+
<p
159+
v-for="(entry, i) in eventLog"
160+
:key="i"
161+
class="text-xs font-mono"
162+
:class="entry.startsWith('+') ? 'text-success' : 'text-error'"
163+
>
164+
{{ entry }}
165+
</p>
166+
<p v-if="eventLog.length === 0" class="text-xs text-on-surface-variant/60">
167+
Events will appear here as you add and remove tasks...
168+
</p>
169+
</div>
170+
</div>
171+
</div>
172+
</template>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<script setup lang="ts">
2+
import { createRegistry } from '@vuetify/v0'
3+
import { computed, shallowRef } from 'vue'
4+
5+
import { provideTaskRegistry, type TaskTicketInput } from './context'
6+
7+
const registry = createRegistry<TaskTicketInput>({ events: true })
8+
const version = shallowRef(0)
9+
const eventLog = shallowRef<string[]>([])
10+
11+
registry.on('register:ticket', ticket => {
12+
eventLog.value = [...eventLog.value.slice(-4), `+ Registered "${ticket.value}"`]
13+
})
14+
15+
registry.on('unregister:ticket', ticket => {
16+
eventLog.value = [...eventLog.value.slice(-4), `- Removed "${ticket.value}"`]
17+
})
18+
19+
registry.onboard([
20+
{ id: 'task-1', value: 'Set up CI pipeline', priority: 'high', done: false },
21+
{ id: 'task-2', value: 'Write unit tests', priority: 'medium', done: false },
22+
{ id: 'task-3', value: 'Update README', priority: 'low', done: true },
23+
{ id: 'task-4', value: 'Review PR #42', priority: 'high', done: false },
24+
{ id: 'task-5', value: 'Refactor auth module', priority: 'medium', done: false },
25+
])
26+
27+
const tasks = computed(() => {
28+
void version.value
29+
return registry.values()
30+
})
31+
32+
function addTask (text: string, priority: TaskTicketInput['priority']) {
33+
registry.register({ value: text, priority, done: false })
34+
version.value++
35+
}
36+
37+
function removeTask (id: string | number) {
38+
registry.unregister(id)
39+
version.value++
40+
}
41+
42+
function toggleDone (id: string | number) {
43+
const ticket = registry.get(id)
44+
if (!ticket) return
45+
registry.upsert(id, { done: !ticket.done })
46+
version.value++
47+
}
48+
49+
function clearCompleted () {
50+
registry.offboard(tasks.value.filter(t => t.done).map(t => t.id))
51+
version.value++
52+
}
53+
54+
provideTaskRegistry({ tasks, eventLog, addTask, removeTask, toggleDone, clearCompleted })
55+
</script>
56+
57+
<template>
58+
<slot />
59+
</template>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { createContext } from '@vuetify/v0'
2+
import type { ID, RegistryTicket, RegistryTicketInput } from '@vuetify/v0'
3+
import type { ComputedRef, ShallowRef } from 'vue'
4+
5+
export interface TaskTicketInput extends RegistryTicketInput<string> {
6+
priority: 'low' | 'medium' | 'high'
7+
done: boolean
8+
}
9+
10+
export type TaskTicket = RegistryTicket<string> & TaskTicketInput
11+
12+
export interface TaskContext {
13+
tasks: ComputedRef<readonly TaskTicket[]>
14+
eventLog: ShallowRef<string[]>
15+
addTask: (text: string, priority: TaskTicketInput['priority']) => void
16+
removeTask: (id: ID) => void
17+
toggleDone: (id: ID) => void
18+
clearCompleted: () => void
19+
}
20+
21+
export const [useTaskRegistry, provideTaskRegistry] = createContext<TaskContext>('v0:task-registry')
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script setup lang="ts">
2+
import TaskConsumer from './TaskConsumer.vue'
3+
import TaskProvider from './TaskProvider.vue'
4+
</script>
5+
6+
<template>
7+
<TaskProvider>
8+
<TaskConsumer />
9+
</TaskProvider>
10+
</template>

0 commit comments

Comments
 (0)