Skip to content

Commit 407e7e8

Browse files
committed
continues efforts to moderniz zero
1 parent 5f5d828 commit 407e7e8

File tree

22 files changed

+301
-253
lines changed

22 files changed

+301
-253
lines changed

.beads/issues.jsonl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
{"id":"battle_mode-iwh","title":"Code based targets","description":"","status":"open","priority":2,"issue_type":"feature","created_at":"2025-12-11T17:07:14.597012-07:00","updated_at":"2025-12-11T17:07:14.597012-07:00"}
2626
{"id":"battle_mode-jbi","title":"Remove AI avatars, use GitHub avatars only","description":"Remove all AI avatar generation code and switch to using GitHub avatars exclusively. Clean up related API endpoints and UI components.","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-12-11T17:07:14.843567-07:00","updated_at":"2025-12-18T13:19:43.893916-07:00","closed_at":"2025-12-18T13:19:43.893916-07:00"}
2727
{"id":"battle_mode-jji","title":"Remove default padding and margin on battle view","description":"Remove default padding and margin in src/routes/(blank)/battle/[id]/code/","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-11T17:26:55.496068-07:00","updated_at":"2025-12-12T11:01:56.083143-07:00","closed_at":"2025-12-12T11:01:56.083143-07:00"}
28-
{"id":"battle_mode-lfg","title":"Playlists of challenges","description":"Allow users to create and share curated playlists of challenges/targets that can be played in sequence","status":"open","priority":1,"issue_type":"epic","created_at":"2025-12-18T10:16:10.757898-07:00","updated_at":"2025-12-18T11:13:47.41964-07:00"}
29-
{"id":"battle_mode-lfg.1","title":"Add playlists table to schema","description":"Create playlists table with id, name, description, creator_id, visibility (public/private), created_at","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-18T10:16:14.684067-07:00","updated_at":"2025-12-18T10:16:14.684067-07:00","dependencies":[{"issue_id":"battle_mode-lfg.1","depends_on_id":"battle_mode-lfg","type":"parent-child","created_at":"2025-12-18T10:16:14.685634-07:00","created_by":"daemon"}]}
28+
{"id":"battle_mode-lfg","title":"Playlists of challenges","description":"Allow users to create and share curated playlists of challenges/targets that can be played in sequence","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-18T10:16:10.757898-07:00","updated_at":"2025-12-18T17:57:44.044541-07:00","closed_at":"2025-12-18T17:57:44.044541-07:00"}
29+
{"id":"battle_mode-lfg.1","title":"Add playlists table to schema","description":"Create playlists table with id, name, description, creator_id, visibility (public/private), created_at","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T10:16:14.684067-07:00","updated_at":"2025-12-18T17:57:44.132265-07:00","closed_at":"2025-12-18T17:57:44.132265-07:00","dependencies":[{"issue_id":"battle_mode-lfg.1","depends_on_id":"battle_mode-lfg","type":"parent-child","created_at":"2025-12-18T10:16:14.685634-07:00","created_by":"daemon"}]}
3030
{"id":"battle_mode-lfg.2","title":"Add playlist_items junction table","description":"Create playlist_items table linking playlists to targets with ordering (playlist_id, target_id, position)","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-18T10:16:15.772136-07:00","updated_at":"2025-12-18T10:16:15.772136-07:00","dependencies":[{"issue_id":"battle_mode-lfg.2","depends_on_id":"battle_mode-lfg","type":"parent-child","created_at":"2025-12-18T10:16:15.772851-07:00","created_by":"daemon"},{"issue_id":"battle_mode-lfg.2","depends_on_id":"battle_mode-lfg.1","type":"blocks","created_at":"2025-12-18T10:16:25.132299-07:00","created_by":"daemon"}]}
3131
{"id":"battle_mode-lfg.3","title":"Create playlist management UI","description":"UI for creating, editing, and deleting playlists. Include drag-and-drop reordering of challenges","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-18T10:16:16.906176-07:00","updated_at":"2025-12-18T10:16:16.906176-07:00","dependencies":[{"issue_id":"battle_mode-lfg.3","depends_on_id":"battle_mode-lfg","type":"parent-child","created_at":"2025-12-18T10:16:16.906866-07:00","created_by":"daemon"},{"issue_id":"battle_mode-lfg.3","depends_on_id":"battle_mode-lfg.2","type":"blocks","created_at":"2025-12-18T10:16:25.248505-07:00","created_by":"daemon"}]}
3232
{"id":"battle_mode-lfg.4","title":"Add playlist browsing and discovery page","description":"Page to browse public playlists, search, and view playlist details","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-18T10:16:17.951101-07:00","updated_at":"2025-12-18T10:16:17.951101-07:00","dependencies":[{"issue_id":"battle_mode-lfg.4","depends_on_id":"battle_mode-lfg","type":"parent-child","created_at":"2025-12-18T10:16:17.951705-07:00","created_by":"daemon"},{"issue_id":"battle_mode-lfg.4","depends_on_id":"battle_mode-lfg.2","type":"blocks","created_at":"2025-12-18T10:16:25.362494-07:00","created_by":"daemon"}]}

.opencode/agent/zero.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ queries:
9494

9595
```ts
9696
import { createBuilder, defineQueries, defineQuery } from '@rocicorp/zero';
97-
import { type } from 'arktype'; // or zod for validation
97+
import { type } from 'arktype';
9898

9999
export const zql = createBuilder(schema);
100100

AGENTS.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,42 @@ example: `bd create --help` shows `--parent`, `--deps`, `--assignee`, etc.
152152
- ❌ Do NOT clutter repo root with planning documents
153153

154154
For more details, see README.md and QUICKSTART.md.
155+
156+
## Zero Query & Mutation Architecture
157+
158+
This project uses Zero 0.25+ with **custom query and mutation endpoints** that
159+
provide auth context to every operation.
160+
161+
### Query Context
162+
163+
All queries receive `ctx: { userID, userRole }` from the server:
164+
165+
```ts
166+
// src/routes/api/query/+server.ts
167+
return query.fn({ args, ctx: { userID, userRole } });
168+
```
169+
170+
### Mutation Context
171+
172+
All mutations receive `ctx: { userId, userRole }` from the server:
173+
174+
```ts
175+
// src/routes/api/mutate/+server.ts
176+
return mutator.fn({ args, tx, ctx: { userId, userRole } });
177+
```
178+
179+
### Key Files
180+
181+
- `src/lib/queries.ts` - All query definitions using `defineQueries`
182+
- `src/lib/mutators.ts` - All mutation definitions using `defineMutators`
183+
- `src/routes/api/query/+server.ts` - Query endpoint (passes context)
184+
- `src/routes/api/mutate/+server.ts` - Mutation endpoint (passes context)
185+
186+
### Important Notes
187+
188+
- **DO NOT** use `definePermissions` from Zero - we handle permissions in
189+
query/mutation definitions via context
190+
- Every query/mutation has access to `userID` and `userRole` via context
191+
- The context is populated from `locals.user` (set by BetterAuth in hooks)
192+
- Anonymous users have `userID: 'anon'` and `userRole: undefined`
193+
- Admin role is `'syntax'`

src/lib/battle_mode/BattlesInProgress.svelte

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
<script lang="ts">
2-
import { z } from '$lib/zero.svelte';
2+
import { z, queries } from '$lib/zero.svelte';
33
4-
let battle = z.createQuery(
5-
z.query.battles.where('status', 'PENDING').related('target')
6-
);
4+
let battle = z.createQuery(queries.battles.byStatus({ status: 'PENDING' }));
75
let battles_started = z.createQuery(
8-
z.query.battles.where('status', 'ACTIVE').related('target')
6+
queries.battles.byStatus({ status: 'ACTIVE' })
97
);
108
</script>
119

src/lib/battle_mode/JoinBattler.svelte

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { files } from '$lib/state/FileState.svelte';
44
import Avatar from '$lib/ui/Avatar.svelte';
55
import { s, to_snake_case } from '$lib/user/utils';
6-
import { z } from '$lib/zero.svelte';
6+
import { z, queries, mutators } from '$lib/zero.svelte';
77
import { type Battle, type Participants, type Target } from '$sync/schema';
88
import { fade, fly } from 'svelte/transition';
99
@@ -15,52 +15,60 @@
1515
me_participant: Participants;
1616
} = $props();
1717
18-
let me = z.createQuery(z.query.user.where('id', z.userID).one());
18+
let me = z.createQuery(queries.user.current());
1919
2020
function join_battle() {
2121
// Make sure target actually exists
2222
if (battle?.target?.name) {
2323
// Create hax with files
24-
z.mutate.hax.insert({
25-
id: crypto.randomUUID(),
26-
user_id: z.userID,
27-
target_id: battle?.target_id || '',
28-
battle_id: battle?.id || '',
29-
html: HTML_TEMPLATE,
30-
css: CSS_TEMPLATE,
31-
type: 'BATTLE'
32-
});
24+
z.mutate(
25+
mutators.hax.insert({
26+
id: crypto.randomUUID(),
27+
user_id: z.userID,
28+
target_id: battle?.target_id || '',
29+
battle_id: battle?.id || '',
30+
html: HTML_TEMPLATE,
31+
css: CSS_TEMPLATE,
32+
type: 'BATTLE'
33+
})
34+
);
3335
3436
// First create file structure for battle
3537
files.create_hax_directory(to_snake_case(battle.target.name));
3638
3739
// Add self as a participant
38-
z.mutate.battle_participants.insert({
39-
id: crypto.randomUUID(),
40-
battle_id: battle?.id || '',
41-
user_id: z.userID,
42-
status: 'PENDING' as const,
43-
display_order: battle?.participants.length || 0
44-
});
40+
z.mutate(
41+
mutators.battle_participants.insert({
42+
id: crypto.randomUUID(),
43+
battle_id: battle?.id || '',
44+
user_id: z.userID,
45+
status: 'PENDING' as const,
46+
display_order: battle?.participants.length || 0
47+
})
48+
);
4549
}
4650
}
4751
4852
function leave_battle() {
4953
if (me_participant && battle?.id) {
50-
z.mutate.battle_participants.delete({
51-
id: me_participant.id
52-
});
54+
z.mutate(
55+
mutators.battle_participants.delete({
56+
id: me_participant.id
57+
})
58+
);
5359
}
5460
}
5561
5662
function lock_in() {
5763
if (me_participant) {
58-
z.mutate.battle_participants.upsert({
59-
id: me_participant.id,
60-
battle_id: battle?.id || '',
61-
user_id: z.userID,
62-
status: 'READY' as const
63-
});
64+
z.mutate(
65+
mutators.battle_participants.upsert({
66+
id: me_participant.id,
67+
battle_id: battle?.id || '',
68+
user_id: z.userID,
69+
status: 'READY' as const
70+
})
71+
);
6472
}
6573
}
6674
</script>

src/lib/battle_mode/Voting.svelte

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import { BATTLE_AWARDS } from '$lib/constants';
3-
import { z } from '$lib/zero.svelte';
3+
import { z, queries, mutators } from '$lib/zero.svelte';
44
import type { Battle, Hax, Participants, User } from '$sync/schema';
55
import { remove_screaming } from '$utils/formatting';
66
@@ -15,32 +15,31 @@
1515
1616
let votes = $derived.by(() =>
1717
z.createQuery(
18-
z.query.battle_votes.where(({ and, cmp }) =>
19-
and(
20-
cmp('battle_id', battle?.id || ''),
21-
cmp('voter_id', z.userID),
22-
cmp('nominee_hax_id', participant?.hax?.id || '')
23-
)
24-
)
18+
queries.battleVotes.myVotesForNominee({
19+
battleId: battle?.id || '',
20+
nomineeHaxId: participant?.hax?.id || ''
21+
})
2522
)
2623
);
2724
28-
function vote(award: (typeof BATTLE_AWARDS)[number], value: number) {
25+
async function vote(award: (typeof BATTLE_AWARDS)[number], value: number) {
2926
const current_vote = votes.data?.find((v) => v.award_type === award);
3027
if (participant.hax.id) {
31-
z.mutate.battle_votes
32-
.upsert({
33-
id: current_vote?.id || crypto.randomUUID(),
34-
battle_id: battle.id || '',
35-
nominee_hax_id: participant.hax.id,
36-
voter_id: z.userID,
37-
award_type: award,
38-
value
39-
})
40-
.catch((error) => {
41-
console.error('Error voting in battle:', error);
42-
alert('Failed to submit vote. Please try again.');
43-
});
28+
try {
29+
await z.mutate(
30+
mutators.battle_votes.upsert({
31+
id: current_vote?.id || crypto.randomUUID(),
32+
battle_id: battle.id || '',
33+
nominee_hax_id: participant.hax.id,
34+
voter_id: z.userID,
35+
award_type: award,
36+
value
37+
})
38+
).server;
39+
} catch (error) {
40+
console.error('Error voting in battle:', error);
41+
alert('Failed to submit vote. Please try again.');
42+
}
4443
}
4544
}
4645
</script>
@@ -53,7 +52,8 @@
5352
value={votes.data?.find((v) => v.award_type === award)?.value || 0}
5453
label="Rating"
5554
style="font-size: 1.6rem;"
56-
onchange={(e) => vote(award, e.target.value)}
55+
onchange={(e: Event & { target: { value: number } }) =>
56+
vote(award, e.target.value)}
5757
></wa-rating>
5858
</div>
5959
{/each}

0 commit comments

Comments
 (0)