From 7970b611d6e04880859eb3fb235209ec03c34f13 Mon Sep 17 00:00:00 2001 From: Amolith Date: Sat, 8 Nov 2025 18:50:18 -0700 Subject: [PATCH] docs(agents): update AGENTS.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reflect ES module split (app.js → render.js/ui.js) - Document tie-breaker protocol (break_tie/tie_broken) - Note daily inactive-room cleanup - Update file structure, noscript fallback, and protocol/testing details Assisted-by: GPT-5 via Crush --- AGENTS.md | 78 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2e120470911fd94d4f808ddacd72130a6d20269e..36f640308c78ad62f46bcb7bc93c2540a78b8d40 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,7 +15,7 @@ Sift is a collaborative list application with real-time voting and ranking. User Tech stack: - Runtime: Deno (TypeScript) - Backend: WebSocket server with SQLite database -- Frontend: Vanilla JavaScript (no frameworks) +- Frontend: Vanilla JavaScript ES modules (no frameworks) - Database: SQLite3 via https://deno.land/x/sqlite3@0.11.1/mod.ts - Server: Deno std HTTP server https://deno.land/std@0.208.0/http/server.ts @@ -53,6 +53,7 @@ User adds items → Client sends 'add_items' → Server inserts to DB → Broadc User votes → Client sends 'vote' → Server upserts vote → Broadcasts vote change User deletes item → Client sends 'delete_item' → Server cascades deletion (and explicitly deletes votes) → Broadcasts deletion User sets room title → Client sends 'set_title' → Server updates room → Broadcasts title change +Top tie present → Client may send 'break_tie' → Server randomly selects a winner among tied top items → Broadcasts 'tie_broken' ``` ### Component Structure @@ -60,20 +61,12 @@ User sets room title → Client sends 'set_title' → Server updates room → Br - server.ts - HTTP request handler serving static files and API endpoints - WebSocket connection manager with room-based broadcasting - - SQLite database with schema initialization, migration (v0→v1→v2→v3), and queries - - Room ID generation via UUID - - SVG icon serving from static/icons/ -- static/app.js - - WebSocket client with message queuing before connection ready - - State management for items, votes, and room title - - Server-authoritative state (no optimistic updates) - - Connection status tracking with last sync time display - - UI disabled state while connecting (isReady flag) - - Keyboard shortcuts (j/k nav, 1/2/3 votes, Enter upvote, e edit, Del delete, Ctrl/Cmd+Z undo, ? help) - - Inline edit flow using 'edit_item' and FLIP-style reordering animation - - Reset votes action (broadcasts 'votes_reset') - - Set room title action (broadcasts 'set_title') - - Deep linking: auto-join room from URL ?room=CODE parameter + - Validation of message size, payload types, and room/item membership + - Schedules daily cleanup of inactive rooms (see db.deleteInactiveRooms) +- db.ts + - SQLite database initialization, foreign keys, schema migration (v0→v1→v2→v3) + - Query helpers for state assembly, item/vote CRUD, room title updates + - Inactive room cleanup by last activity timestamp (created_at of rooms/items/votes) - static/index.html - Two-screen UI: start screen (create/join) and list screen - Connection status indicators (dot + text + last sync time) @@ -81,12 +74,19 @@ User sets room title → Client sends 'set_title' → Server updates room → Br - Help modal markup and keyboard shortcut reference - Input methods: single-item input and bulk textarea - Inline SVG icons embedded in buttons -- static/style.css - - CSS custom properties for theming - - Light/dark mode via prefers-color-scheme - - Responsive design - - Styles for connection status, help modal, selected item, veto state, and animations - - Reduced motion support via @media query + - Loads ES module script with `` +- static/app.js (ES module entry) + - WebSocket client with message queuing before connection ready + - State management for items, votes, selected item, room title + - URL deep-linking and history updates + - Delegates rendering/utilities to render.js and keyboard/help UX to ui.js +- static/render.js + - Sorting, escaping, connection status, last-sync display, and enabled/disabled UI toggling +- static/ui.js + - Keyboard shortcuts (j/k nav, 1/2/3 votes, Enter upvote, e edit, Del delete, Ctrl/Cmd+Z undo, ? help) + - Help modal open/close and minimal press-lock for buttons +- static/style.css, static/palette.css + - CSS custom properties, light/dark theme, responsive layout, reduced motion support ### Database Schema (schema_version = 3) @@ -137,6 +137,7 @@ Server → Client messages { type: 'item_deleted', itemId } { type: 'votes_reset' } { type: 'title_changed', title: string|null } +{ type: 'tie_broken', itemId, text } ``` Client → Server messages @@ -148,9 +149,10 @@ Client → Server messages { type: 'delete_item', itemId } { type: 'reset_votes' } { type: 'set_title', title: string|null } // null to clear title +{ type: 'break_tie' } // request server to randomly choose among tied top items ``` -### State Synchronization +### State Synchronization & Maintenance Connection lifecycle 1. Client connects with ?room=CODE&user=UUID query params @@ -162,7 +164,9 @@ Connection lifecycle Client-side optimism: None — server is authoritative. Client waits for broadcast confirmation. -Room cleanup: When a socket closes, it's removed from the in-memory clients Map. No periodic expiration/cleanup is implemented. +Room cleanup +- Socket close: connection is removed from the in-memory clients Map +- Inactive rooms: daily job deletes rooms with no activity for 30 days (based on max of room/items/votes created_at) Connection status: Client tracks connection state (connecting/connected/disconnected) with visual indicators and last sync timestamp, updated every second. @@ -175,12 +179,16 @@ Client-side sort Visual states - Vetoed items: 40% opacity, max-height constraint, hidden overflow -- Active vote buttons: darker background +- Active vote buttons: darker background (press-lock to avoid flicker) - Selected item: outline with accent color -- Items with active votes: left border colored - Items display current score with +/- prefix +- Highest-scoring non-vetoed items get a left "top-check" indicator - Reordering uses FLIP animation +Tie breaker +- When a top score tie exists, a "Break the tie" UI appears +- Clicking it sends 'break_tie'; server broadcasts 'tie_broken' with the chosen item for display + Room title - Displayed in header when set - Hidden when null/empty @@ -203,6 +211,7 @@ Message validation - edit_item: text sanitized and length-checked (1–200) - reset_votes: clears all votes for items in the room - set_title: title can be null/undefined (clears title) or string; if string, sanitized and length-checked (1–200) +- break_tie: server computes tie among top non-vetoed items and broadcasts a random winner Validation helpers - safeParseMessage(): size check + JSON parse with error handling @@ -255,13 +264,15 @@ Security notes 4) Broadcast 'except' param unused by callers: all clients, including sender, receive broadcasts. 5) Item-room validation enforced: prevents cross-room vote/edit/delete. 6) Port is hardcoded: server always runs on port 8294. -7) Static files served via dedicated routes including SVG icons from /icons/ path. +7) Static files served via dedicated routes; icons are inline in HTML. An /icons/ route exists for optional SVGs if added under static/icons/. 8) No build step: Deno runs TypeScript directly. 9) Database file location: lists.db is created in the project root. 10) Deep linking works: Opening a URL with ?room=CODE auto-joins that room. 11) UI disabled while connecting: Inputs/buttons disabled until 'state' message received (isReady flag). 12) Room title is optional: null/empty titles are hidden; title uses same text sanitization as items. 13) Position restoration after voting: Client remembers position in sorted list and re-selects item at same position after vote-triggered re-sort. +14) ES modules: index.html loads app.js as type=module which imports render.js and ui.js. +15) Cleanup: a daily job deletes rooms with no activity for 30 days; this is irreversible. ## Testing Approach @@ -280,6 +291,8 @@ Core functionality 10. Deep linking: open URL with ?room=CODE; verify auto-join 11. Connection status: watch status dot change and last sync time update 12. Keyboard shortcuts: test j/k navigation, 1/2/3 voting, e edit, Del delete, ? help +13. Top tie: when multiple non-vetoed items share the highest score, "Break the tie" appears; clicking it shows a "Winner: ..." message (tie_broken) +14. Noscript fallback: with JS disabled, verify noscript message displays Validation testing (check server logs) 1. Empty item: whitespace-only text → ignored, logged @@ -295,7 +308,7 @@ Validation testing (check server logs) ## Development Workflow Making changes -1. Edit source files (server.ts or static/*) +1. Edit source files (server.ts, db.ts, or static/*) 2. Restart Deno server (no hot reload) 3. Hard refresh browser (Ctrl+Shift+R) to bypass cache 4. Test in multiple tabs for WebSocket sync @@ -318,16 +331,19 @@ Adding new message types ``` / -├── server.ts # Deno server: HTTP, WebSocket, SQLite +├── server.ts # Deno HTTP/WebSocket server +├── db.ts # SQLite schema, migrations, queries, cleanup ├── lists.db # SQLite database (created on first run) └── static/ ├── index.html # HTML structure - ├── app.js # Client-side JavaScript + ├── app.js # Entry module; WS client and state + ├── render.js # Rendering/util helpers + ├── ui.js # Keyboard/help UI logic ├── style.css # Styles with light/dark theme - └── icons/ # SVG icons (served via /icons/ route) + └── palette.css # Color tokens and CSS variables ``` -No nested directories beyond icons/, no build artifacts, no dependencies beyond Deno stdlib and sqlite3. +No build artifacts, no dependencies beyond Deno stdlib and sqlite3. An optional static/icons/ directory may be added if serving SVGs via /icons/. ## Important Constants