feat(entity-todo): Phase 4 - Local dev experience with Vite HMR#629
Merged
viniciusdacal merged 9 commits intomainfrom Feb 22, 2026
Merged
feat(entity-todo): Phase 4 - Local dev experience with Vite HMR#629viniciusdacal merged 9 commits intomainfrom
viniciusdacal merged 9 commits intomainfrom
Conversation
viniciusdacal
commented
Feb 22, 2026
Contributor
Author
viniciusdacal
left a comment
There was a problem hiding this comment.
Code Review: PR #629 - Phase 4: Local Dev Experience
Role: Primary Review (Frontend & API Surface Engineer)
Summary
This PR implements the local development experience for entity-todo. Overall the implementation is solid.
1. Dev Server ✅
- Uses bun run src/dev-server.ts correctly (Bun is the team runtime)
- Unified server combines Vite + API routes + SPA + SQLite
- Port correctly defaults to 3000 from env
2. Vite HMR Integration ✅
- Properly configured with middlewareMode and appType custom
- HMR clientPort set correctly
- Last commit (d764c8e) correctly removed /api proxy to prevent infinite loop with dev-server middleware
3. SQLite Persistence ✅
- Uses bun:sqlite (correct per team conventions)
- Full DbDriver interface implementation
- Creates data/ directory if missing
- Migration support with proper table creation
- SQL injection protection: Whitelist approach in list() method for WHERE columns
4. Error Handling ✅
- db.ts: Try/catch with user-friendly messages
- entry-server.ts: SSR wrapped in try/catch, returns SPA fallback on error
- worker.ts: D1 binding validation + try/catch in all handlers + security headers
- dev-server.ts: Top-level try/catch returns 500
5. Minor Notes
- The vite.config.ts comment about proxy removal is a bit abrupt but harmless
- Good separation between dev (SQLite) and prod (D1) persistence
Verdict
Approved. Clean implementation that meets all acceptance criteria.
viniciusdacal
commented
Feb 22, 2026
Contributor
Author
viniciusdacal
left a comment
There was a problem hiding this comment.
Adversarial Review: Local Dev Experience
Found several issues that could cause problems for developers:
Critical Issues
-
Package Manager Confusion (DX Footgun)
- package.json uses bun run for dev script (correct for team)
- But docs/comments reference pnpm dev, pnpm build && pnpm preview
- Developers will be confused about which package manager to use
-
Hardcoded localhost in Request conversion
- toWebRequest() hardcodes http://localhost:PORT
- Breaks in containerized dev environments or reverse proxies
-
PR Has Merge Conflicts
- This PR is marked as CONFLICTING
- Cannot be merged until resolved
Medium Issues
-
No CORS Headers
- API at /api/* has zero CORS configuration
- Developers calling API from separate frontend origins will fail
-
Database Not Closed on Shutdown
- SQLite driver close() is never called in graceful shutdown
- Can leave WAL files in inconsistent state
-
Console.log in Production Code
- console.log(Entity Todo app mounted) in index.ts runs in all environments
- Should be wrapped in import.meta.DEV
-
No Request Body Validation
- POST/PATCH handlers dont validate Content-Type
- Accepts any body without checking its valid JSON
Minor Issues
-
No Rate Limiting
- SQLite dev server has no rate limiting
-
No Input Length Limits
- No max length on title field
Summary
The core functionality works, but the DX has several footguns. Package manager mismatch and missing CORS are most impactful.
added 8 commits
February 22, 2026 20:35
- Add entry-server.ts: SSR render function using renderPage() - Add entry-client.ts: Client hydration with hydrate()/mount() fallback - Update worker.ts: Route splitting (/api/* → JSON, /* → SSR) - Update vite.config.ts: SSR build configuration for worker + client bundle - Add wrangler.toml: Cloudflare Workers configuration with static assets - Add @vertz/cloudflare and @cloudflare/workers-types dependencies This establishes the SSR + Hydration pipeline for entity-todo on Cloudflare Workers.
Critical fixes:
1. Hydration: Use mount() instead of hydrate() with empty registry
- The registry-based hydrate() was receiving {} so no components were found
- Now using mount() which replaces SSR content with client-rendered app
2. API routes: Wire up actual CRUD handlers instead of stubs
- Implemented in-memory todo store with full CRUD operations
- Returns proper JSON responses that match SDK expectations
3. Error handling: Wrap renderApp() in try/catch
- On error, returns fallback HTML that loads client bundle (SPA fallback)
- Includes error message for debugging
4. Streaming: Already using renderToStream via renderPage
- No changes needed, this was already implemented
High priority:
5. Security headers: Add basic headers to all responses
- X-Content-Type-Options, X-Frame-Options, X-XSS-Protection
- Referrer-Policy, Content-Security-Policy
6. CSS injection: Include CSS in rendered page
- Added inline <style> tag with globalStyles.css in head option
1. entry-client.ts: Use 'tolerant' hydration mode instead of replace - mount() now accepts hydration: 'tolerant' to walk existing SSR DOM nodes - This preserves server-rendered content while attaching event handlers 2. worker.ts: Replace in-memory store with D1 database adapter - Use createDb() with SQLite dialect and D1 binding - Wire CRUD routes to use the database instead of Map 3. mount.ts: Add 'tolerant' hydration option - Updated MountOptions interface to support 'tolerant' mode - In tolerant mode, app doesn't clear existing DOM content 4. wrangler.toml: Add D1 database binding configuration
- Revert mount.ts to version from main (proper tolerant hydration) - Add D1 binding validation in worker.ts with security headers - Simplify entry-client.ts to just call mount with hydration: 'tolerant'
- Export globalStyles from index.ts (entry-server.ts) - Import D1Database from @cloudflare/workers-types (worker.ts) - Import isOk from @vertz/fetch instead of @vertz/ui (todo-list.tsx) Add type tests to verify correct imports.
Phase 4: Local dev experience - Add dev-server.ts that brings together: - Vite HMR for UI hot-reload - @vertz/server for API routes - SPA mode for client-side rendering - SQLite for local persistence (not D1) - Update package.json scripts: - dev: starts the unified dev server - build: builds the production bundle - deploy: runs wrangler deploy - Fix index.ts to only mount in browser (not SSR) - Update entry-server.ts to not import client-side code The dev server runs at http://localhost:3000 with: - API routes at /api/* - Client-side rendering with HMR - SQLite database persistence in data/todos.db For true SSR, run: pnpm build && pnpm preview Closes #616
…Styles - Remove /api proxy from vite.config.ts (handled by dev-server.ts middleware) - Add try/catch around SQLite initialization in db.ts with clear error message - Import globalStyles from ./index instead of duplicating in entry-server.ts
d764c8e to
77175c1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 4: Local dev experience for entity-todo example.
Changes
New dev-server.ts - Unified dev server that brings together:
Updated package.json scripts:
Fixes:
Usage
📦 SQLite database initialized at: /Users/viniciusdacal/openclaw-workspace/vertz/examples/entity-todo/data/todos.db
🚀 Starting Vertz Dev Server...
✅ Vite dev server initialized
╔═══════════════════════════════════════════════════════════╗
║ ║
║ 🏗️ Vertz Dev Server ║
║ ║
║ Local: http://localhost:3000 ║
║ API: http://localhost:3000/api ║
║ ║
║ Stack: ║
║ • Vite HMR (UI hot-reload) ✅ ║
║ • @vertz/server (API routes) ✅ ║
║ • SPA mode (client-side rendering) ║
║ • SQLite (local persistence) ✅ ║
║ ║
║ Notes: ║
║ • API routes served locally with SQLite ║
║ • UI uses Vite HMR for hot-reload ║
║ • For SSR, run: pnpm build && pnpm preview ║
║ ║
║ Available endpoints: ║
║ • GET /api/todos List all todos ║
║ • GET /api/todos/:id Get a todo ║
║ • POST /api/todos Create a todo ║
║ • PATCH /api/todos/:id Update a todo ║
║ • DELETE /api/todos/:id Delete a todo ║
║ ║
╚═══════════════════════════════════════════════════════════╝
👋 Shutting down...
The dev server provides:
For true SSR, run:
Acceptance Criteria
pnpm devstarts everything with one commandCloses #616