Frontend UI Architecture¶
Architectural reference for VIBE's frontend UI patterns. Optimized for LLM consumption.
See also:
core.md- Core VIBE engine (probing, sessions, handlers)
1. DESIGN PHILOSOPHY¶
VIBE uses a server-driven UI with minimal client-side JavaScript. Two lightweight libraries handle all frontend interactivity: htmx for server communication and Alpine.js for ephemeral UI state.
Principles:
- Server is source of truth for application state
- Client handles only transient UI state (dropdowns, tabs)
- No heavy SPA framework -- minimal JS footprint
2. HTMX (SERVER-DRIVEN UPDATES)¶
Purpose: AJAX requests and DOM updates via HTML attributes -- no custom JavaScript needed for most interactions.
Key Patterns:
hx-post,hx-get-- Form submissions and data fetchinghx-swap="outerHTML"-- Replace elements with server responsehx-swap-oob="true"-- Out-of-band swaps for updating multiple elementshx-trigger="change"-- Auto-submit on field changessse-connect,sse-swap-- Server-Sent Events for streaming
Primary Use Cases:
- Interview form auto-submission (
/process/<template_id>/@<version>/) - Partial page updates without reload
- Real-time streaming for assistant chat
- Dynamic question panel updates (add/remove/reorder via OOB swaps)
Key Files:
vibe/static/htmx.min.js-- htmx libraryvibe/static/sse.min.js-- SSE extension for streamingvibe/web/htmx.py-- OOB swap helpers (OOBSwap,render_oob_div)vibe/web/core/html_fragments.py-- HTML fragment rendering utilities
3. ALPINE.JS (CLIENT-SIDE INTERACTIVITY)¶
Purpose: Lightweight reactive framework for ephemeral UI state that doesn't need server persistence.
CRITICAL: CSP Build Constraints
VIBE uses the CSP-compliant build (alpine-csp.min.js) -- no unsafe-eval required. This means inline JavaScript expressions are NOT supported:
| NOT Allowed (Regular Alpine) | Required (Alpine CSP) |
|---|---|
x-data="{ count: 0 }" |
x-data="componentName" (registered component) |
@click="count++" |
@click="increment" (method on component) |
x-data="myFunction()" |
x-data="myComponent" (no parentheses) |
How to Add New Alpine Components:
-
Define in
vibe/static/alpine-components.jsusingAlpine.data(): -
Use in templates WITHOUT parentheses:
-
Pass dynamic data via
data-*attributes, read withthis.$el.dataset.
Common Mistake: Writing x-data="myComponent()" with parentheses will fail silently. The CSP build cannot evaluate function calls.
Custom Components: devControls, fileUpload, reviewWorkbench (in review.js)
Custom Stores: $store.tabs, $store.assistantStatus
Custom Directives: x-autogrow (textarea auto-expand), x-autoscroll (container auto-scroll)
Note: The Alpine Collapse plugin is NOT installed. Do not use x-collapse.
Key Files:
vibe/static/alpine-csp.min.js-- Alpine CSP buildvibe/static/alpine-components.js-- Custom stores, directives, and componentsvibe/review/static/review.js-- Review module components
4. INTEGRATION PATTERN¶
User Action
|
Alpine.js handles ephemeral state (dropdown open, tab active)
|
htmx sends request to server (hx-post, hx-get)
|
Server renders HTML fragment (Jinja templates)
|
htmx swaps fragment into DOM (hx-swap, hx-swap-oob)
|
Alpine.js reinitializes on new content
5. DEVELOPER MODE UI ENDPOINTS¶
Location: vibe/web/routes/ui_helpers.py
Server-rendered endpoints providing developer diagnostics and dynamic UI fragments. Most require developer mode (is_devel()) to be active.
Explain Relevance (/explain/<template_id>/<path:question_path>) -- Developer-mode endpoint showing why a question is relevant. Displays active/inactive usage paths with condition evaluation, probe access diagnostics (accessed_keys, accessed_locations, needed_vars), and parent-dependency ancestry. Uses _safe_eval_condition() with template_env.overlay(undefined=jinja2.Undefined) to safely evaluate compound conditions without ProbeMissingPathError.
Remaining Questions (/remaining_questions/<template_id>/) -- HTMX endpoint returning unanswered required questions using find_unanswered_required(). Questions are sorted by template config order.
Appendix Preview (/preview/<template_id>/appendix/<alias>) -- Renders preview of appendix content. Builds context mirroring insert() precedence (host defaults -> component defaults -> passthrough -> actual answers). Uses web preview mode (ChainableUndefined via _is_web_preview) for graceful degradation when not all variables are answered.
Locked Field Reasons -- _get_lock_source() (vibe/web/core/rendering.py) returns the ValueSource (SESSION_CONTEXT, LINKED) for locked questions. _locked_reason_text() provides localized tooltip text. Handlers pass locked_reason via QuestionRenderContext to templates for tooltip display on lock icons.
6. FILE LOCATION INDEX¶
| What | Where |
|---|---|
| htmx library | vibe/static/htmx.min.js |
| SSE extension | vibe/static/sse.min.js |
| Alpine.js CSP build | vibe/static/alpine-csp.min.js |
| Alpine components | vibe/static/alpine-components.js |
| Review components | vibe/review/static/review.js |
| Main UI script | vibe/static/script.js |
| HTML templates | vibe/web/templates/ |
| Layout templates | vibe/web/templates/layout/ |
| Question widgets | vibe/web/templates/question_types/ |
| OOB swap helpers | vibe/web/htmx.py |
| HTML fragment utils | vibe/web/core/html_fragments.py |
| UI helper routes | vibe/web/routes/ui_helpers.py |
| Question rendering | vibe/web/core/rendering.py |
Document Version: 1.2 Last Updated: 2026-03-22 Notes: Updated file paths for package reorganization: vibe/web_core/ merged into vibe/web/core/ (Python) and vibe/web/templates/ (HTML)