Skip to content

feat: Helios Booth 2026 — Lit web component voting booth#1

Open
benadida-agent wants to merge 33 commits into
masterfrom
feat/booth2026-implementation-plan
Open

feat: Helios Booth 2026 — Lit web component voting booth#1
benadida-agent wants to merge 33 commits into
masterfrom
feat/booth2026-implementation-plan

Conversation

@benadida-agent

@benadida-agent benadida-agent commented Feb 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • New heliosbooth2026/ directory implementing a complete voting booth using Lit 3.3.x + TypeScript 5.x + Vite 6.x
  • Full end-to-end voting flow: election info → question selection → ballot encryption (Web Worker) → review → submit/audit
  • 5 screen components (question-screen, review-screen, submit-screen, audit-screen, encrypting-screen) orchestrated by central booth-app state holder
  • Accessibility: skip link, ARIA landmarks, focus management, keyboard navigation, high contrast & reduced motion support
  • Production-ready: Vite single-bundle build (61 KB JS gzip 17 KB), responsive CSS, error handling with recovery, noscript fallback
  • Django URL route at /booth2026/ serves the new booth alongside the existing heliosbooth/

Architecture

  • Props-drilling pattern: booth-app holds all state, child components receive data via properties and emit events via CustomEvent with bubbles: true, composed: true
  • Encryption: Web Worker (encryption-worker.js) handles ballot encryption in background thread using existing jscrypto libraries
  • Crypto libraries: Loaded via synchronous <script> tags (same as existing booth), TypeScript declarations in src/crypto/types.ts
  • No runtime dependencies beyond Lit: All crypto handled by existing jscrypto, no additional frameworks

Test Plan

  • All 198 Django tests pass
  • TypeScript compiles with strict mode (including noUnusedLocals, noUnusedParameters)
  • Vite production build succeeds
  • No network calls between Start and Submit (offline-capable encryption)
  • Manual: Load booth at /booth2026/?election_url=<url>, complete full voting flow
  • Manual: Test keyboard-only navigation through entire flow
  • Manual: Verify ballot audit (spoil + re-encrypt) works
  • Manual: Test on mobile viewport (responsive layout)

🤖 Generated with Claude Code

benadida and others added 29 commits February 8, 2026 15:33
Four-phase implementation plan for rebuilding the voting booth with
Lit web components, TypeScript, and Vite (booth2026).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy crypto libraries from the legacy booth to the new Lit-based booth2026:
- Copy jscrypto directory with all crypto libraries (jsbn, sjcl, bigint, elgamal, helios, sha1, sha2)
- Copy underscore-min.js utility library
- Add TypeScript type declarations for crypto globals (BigInt, ElGamal, HELIOS, etc.)
- Types enable type-safe crypto operations in the voting booth

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create src/main.ts as the entry point that imports booth-app component
- Create src/booth-app.ts with BoothApp LitElement component that:
  - Manages voting booth screen state (loading, election, question, review, submit, audit)
  - Holds voter state including election data, answers, and encrypted ballot
  - Implements election loading from server with metadata support
  - Provides screen navigation and exit functionality
  - Renders election info screen with start button
  - Includes placeholder screens for Phase 2-3 work
- Update crypto/types.ts to remove global declarations (use window access instead)
- Adjust tsconfig.json to disable unused locals/params checking for Phase 1 shell

This provides the main application shell for the voting booth with state management
ready for Phase 2 (voting flow) and Phase 3 (submission/audit).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Load jscrypto libraries and underscore before the app to ensure crypto globals
are available when booth-app initializes. Scripts are loaded synchronously in
dependency order before the module script that imports booth-app.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix crypto script paths from root-absolute (/lib/jscrypto/) to relative (../lib/jscrypto/) to work correctly when served at /booth2026/ in production
- Add typed declare global block in crypto/types.ts for non-conflicting globals (HELIOS, b64_sha256, ElGamal, Random, UTILS, sjcl) with proper type narrowing
- Update booth-app.ts to use declared typed globals directly instead of (window as any) casting
- Move inline styles to CSS classes (.start-button-container and .help-email) in booth-app.ts static styles
- Export BoothScreen type for child component usage in later phases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…avigation

Implement Task 2 of Phase 2 - integrate question screen component into booth app:
- Add imports for question-screen component and event types
- Implement handleAnswerChange to track answer selections with immutable updates
- Implement validateCurrentQuestion to enforce minimum answer requirements
- Implement handleNavigation to handle previous/next/review navigation with validation
- Add goToQuestion method for editing answers from review screen
- Add renderQuestionScreen method to render question with proper answer orderings
- Update startVoting to show review button immediately for single-question elections
- Update renderCurrentScreen to use renderQuestionScreen instead of placeholder

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Important Fix 1: Remove redundant checkbox accessibility attributes from li element. Native input type="checkbox" handles the semantics; role="checkbox", aria-checked, and aria-disabled on the li create duplicate announcements for screen readers.
- Important Fix 2: Change warning-box color from var(--color-success, #28a745) to var(--color-text-secondary, #666) for proper semantic coloring of constraint messages.
- Minor Fix 1: Skip validation in handleNavigation() for 'previous' direction to allow users to navigate back without meeting minimum answer requirements.
- Minor Fix 2: Use explicit undefined comparisons in getConstraintText() to properly handle cases where min=0 or max=0.

Verification:
- TypeScript: npx tsc --noEmit ✓
- Build: npm run build ✓
- Tests: uv run python manage.py test -v 0 → 198 tests pass ✓

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Task 7 implementation:
- Added screen imports for review, submit, audit, and encrypting screens
- Updated BoothScreen type to include 'encrypting' state
- Added encryption-related state properties: worker, encryptionProgress, answerTimestamps, dirty tracking, encryptedBallot, auditTrail, rawElectionJson
- Implemented worker initialization and async encryption handling
- Updated loadElection to store rawElectionJson and initialize worker
- Updated handleAnswerChange to mark questions as dirty
- Updated handleNavigation to trigger sealBallot on review
- Added handleReviewNavigation, prepareForCast, and auditBallot handlers
- Added handleAuditNavigation, resetAndReencrypt, and postAuditedBallot methods
- Added handleBallotSubmit for submission flow
- Updated renderCurrentScreen with cases for all new screens
- Updated getProgressStep to include encrypting screen
- Updated beforeunload handler to warn on encrypting screen
- Added getPrettyChoices and worker encryption helper methods
- Fixed EncryptedVote interface to support includeRandomness parameter and optional clearPlaintexts method

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Critical: Add worker.onerror handler to catch encryption failures and set error state
- Important: Fix worker instantiation to use import.meta.url instead of root-absolute path
- Important: Fix encrypting.gif and loading.gif paths to use import.meta.url for production
- Important: Remove dead hidden form from review-screen (submission is in submit-screen)
- Minor: Replace `as any` with `Record<string, unknown>` in hasClearPlaintexts type guard
- Minor: Make auditExpanded @State() reactive and remove manual requestUpdate()
- Minor: Move inline styles to CSS classes in review-screen, audit-screen, submit-screen

All TypeScript compilation passes, build succeeds, and all 198 Django tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…gement

Implements Task 1 from Phase 4 accessibility enhancements:
- Add skip link element with proper styling for keyboard navigation
- Add ARIA landmarks: header with role="banner", nav with aria-label
- Add focus management via focusMainContent() method for screen transitions
- Call focusMainContent() in sealBallot(), prepareForCast(), and auditBallot()
- Update render() to use proper semantic HTML structure
- Add aria-live="assertive" to error alert for screen reader announcements
- getProgressStep() method already exists and works correctly

The main element now has id="main-content" and tabindex="-1" for focus management.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement Task 2 from Phase 4 with:
- isInitializing state property for tracking booth initialization
- renderErrorScreen() method with recovery options (Reload, Return to Election)
- Error screen CSS styles (.error-screen, .error-message, .error-actions)
- Improved initializeBooth() with error classification (network, crypto, generic)
- New waitForCryptoWithTimeout() method with 10-second timeout
- Updated renderCurrentScreen() loading case with initialization message
- Updated election case to show error screen when initialization fails
- Enhanced loading display with role="status" and aria-live="polite"
- loading-detail style for secondary messaging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update vite.config.ts with production build options:
  - Add sourcemaps for debugging
  - Configure terser minification
  - Add dev server proxy for Django API calls
  - Set publicDir to 'public'
  - Use hashed filenames for assets
- Update index.html with metadata and production paths:
  - Add SEO meta tags (description, robots noindex)
  - Add noscript fallback for JavaScript requirement
  - Use relative paths for crypto scripts and styles
  - Add preload hints for critical assets
- Create public/ directory and move GIF files:
  - Move encrypting.gif to public/
  - Move loading.gif to public/
  - Update references in encrypting-screen.ts and review-screen.ts
  - Add @vite-ignore comments to suppress build warnings
- Install terser as dev dependency for minification
- Verified production build succeeds with single bundle

Build output:
- dist/assets/booth.[hash].js (61KB)
- dist/assets/booth.[hash].css (3KB)
- Source maps generated for debugging
- Hashed filenames for cache busting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add documentation comment to the booth2026 URL route explaining how to
switch from serving source files in development to serving built dist
files in production deployment.
…uild

Move encrypting.gif and loading.gif from root heliosbooth2026/ to public/
directory so they are properly included in the production build output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical (C1): Fix aria-current attribute binding on progress steps
- Changed from string interpolation (which Lit ignores) to proper conditional
  attribute binding using aria-current=${condition ? 'step' : nothing}
- Imported 'nothing' from 'lit' for proper undefined handling

Important (I1): Add focusMainContent() to startVoting() and goToQuestion()
- Both screen transition methods now call focusMainContent() like other
  transitions (sealBallot, prepareForCast, auditBallot) for consistent
  focus management and accessibility

Important (I2): Remove redundant role="main" from main element
- The <main> element has implicit role="main", explicit attribute was
  redundant and violates HTML best practices

Minor (M1): Consolidate duplicate .error and .error-message styles
- Both classes had identical background, border, color, padding, and
  border-radius CSS. Now consolidated with shared rule and context-
  specific margin handling for .error-screen .error-message

Minor (M2): Remove useless preload link from index.html
- The <link rel="preload"> before the actual stylesheet provided zero
  benefit and cluttered the HTML

All issues verified:
- TypeScript compiles without errors (npx tsc --noEmit)
- Production build succeeds (npm run build)
- No regressions introduced

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- C1: Fix crypto script paths in index.html - change ../lib/ to lib/ to resolve correctly when served from /booth2026/
- I1: Re-enable noUnusedLocals and noUnusedParameters in tsconfig.json and fix unused cryptoReady state
- I2: Clarify production deployment comment in urls.py - serve full heliosbooth2026/ directory (not just dist/) because crypto libs in lib/ are needed
- I3: Fix choices[index]?.length < question.max comparison - use nullish coalescing (?? 0) to avoid undefined comparison
- M1: Change encryptedBallot type from unknown to EncryptedVote | null and remove unnecessary type casts
- M2: Merge duplicate import statements from crypto/types into single import

All issues fixed:
✓ TypeScript compilation passes with strict unused checking
✓ npm run build succeeds
✓ Django test suite passes (198/198 tests)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@benadida-agent benadida-agent changed the title Add implementation plans for Helios Booth Lit Redesign feat: Helios Booth 2026 — Lit web component voting booth Feb 8, 2026
benadida and others added 4 commits February 8, 2026 18:44
… dist on build

- Update Django URL to serve from heliosbooth2026/dist/ instead of source
- Add explicit route for /booth2026/ to serve index.html (directory index)
- Update npm build script to copy lib/ into dist/ for crypto library access

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Heroku runs `heroku-postbuild` which installs dependencies and builds
the heliosbooth2026 Lit app into dist/. Requires adding the Node.js
buildpack before the Python buildpack:

  heroku buildpacks:add --index 1 heroku/nodejs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Heroku prunes devDependencies before running heroku-postbuild, so tsc
and vite aren't available. Move them to dependencies so they're
installed when the build script runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix ballot submission broken by Shadow DOM: switch submit-screen to
light DOM rendering via createRenderRoot() so the <form> can POST
natively. Add single ballot verifier (verify-screen component, worker,
verify.html entry point) and fix audit screen verifier link.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants