πŸ“š Zap Platform Documentation

Complete documentation for the Zap platform, including development guides, testing procedures, and infrastructure details

CODING_HISTORY
Development problems and solutions β€’ Core Documentation

Zap Coding History

This file documents all the major problems, bugs, and nightmares encountered during development, along with their solutions. This file is append-only - never delete or replace content, only add new entries.

---

February 2026

Feature: Windsurf Chat Capture -- VSCode Extension, API, and Web UI

Date: 2026-02-25 Scope: Capture Windsurf chat conversations to PostgreSQL, bypassing encrypted storage

Changes:

  • Created VSCode extension (apps/windsurf-chat-capture/) in TypeScript with two capture modes:
  • - Manual capture: reads clipboard content, extracts user/assistant messages, sends to API - Automatic capture: monitors editor changes with heuristics to detect chat content
  • Built API endpoints (web/public/projects/api/):
  • - windsurf-hook.php -- receives Cascade Hook POSTs from Windsurf (pre_user_prompt, post_cascade_response) - windsurf-manual-capture.php -- receives manual captures from extension with messages array - windsurf-chats.php and windsurf-plans.php -- list/filter/search endpoints
  • Created database schema (database/migrations/2026-02-25-0001-windsurf-chat-schema.sql):
  • - windsurf_chat_events -- raw hook events with unique (execution_id, hook_type) constraint - windsurf_chats -- aggregated conversations by trajectory_id with full-text search - windsurf_chat_projects -- many-to-many project associations
  • Built Windsurf Hub web UI (web/public/projects/windsurf.php) with:
  • - Manual capture form (chat title + content paste) - Workspace filtering pills and search - Card/list toggle views for chats and plans
  • Created hook scripts (scripts/):
  • - windsurf-capture-hook.py -- Python script processing JSON from Windsurf Cascade Hooks - windsurf-capture-hook.bat -- Windows batch wrapper for clipboard stdin handling - windsurf-hook-config.json -- Cascade Hook configuration with pre_user_prompt and post_cascade_response events
  • Packaged extension with vsce package producing .vsix file for manual installation
  • Added PNG icon support for Cursor and Windsurf hubs (web/public/projects/icons/cursor.png and windsurf.png)
  • Problems encountered:

    • Extension payload format mismatch: Initial manual capture sent {id, workspace, user_message, cascade_response} but API expected {sessionId, timestamp, workspace, messages: [{role, content, timestamp}]}. Fixed by restructuring JSON payload to match API schema.
    • SVG icons didn't match brand logos: First attempt at creating SVG icons from scratch produced distorted shapes that didn't match actual Cursor hexagon or Windsurf wave logos. Solution: switched to PNG format and updated code references from .svg to .png in index.php renderCards and renderList functions.
    • Empty PNG files in assets: Uploaded images appeared as 0-byte files in /home/jd/assets/. Root cause: image upload mechanism didn't persist binary data properly. Workaround: save PNG files directly to /var/www/zap/web/public/projects/icons/ location.
    • Decisions made:

    • PNG over SVG for brand logos -- actual logo files are more accurate than hand-coded SVG approximations
    • Manual capture as primary mechanism -- automatic capture heuristics are fallback until Windsurf exposes official API
    • Same database patterns as cursor_chats -- reuse proven schema design with windsurf_ prefix
    • ---

      February 2026

      Feature: Zap-RAG shared service -- design phase

      Date: 2026-02-23 Scope: New shared RAG service, research, architecture planning

      Changes:

    • Created apps/zap-rag/ as a new shared RAG service within the zap-orcus monorepo.
    • Analysed 15+ arxiv papers in full: Late Chunking (2409.04701), RAPTOR (2401.18059), CRAG (2401.15884), Self-RAG (2310.11511), ColPali (2407.01449), HiRAG (2503.10150), Chain-of-Density (2309.04269), BGE-M3 (2402.03216), Skeleton-of-Thought (2307.15337), pgvectorscale (Timescale), CAR (2511.14769), plus several additional surveys and technique papers.
    • Submitted the plan to three frontier models (ChatGPT, Gemini, Perplexity) for critique. Each returned detailed feedback with disagreements on proposition indexing, PHP/Apache performance, HyDE effectiveness, and confidence scoring.
    • Resolved all disagreements using paper evidence: proposition indexing made opt-in, PHP/Apache retained for current LAN scale, HyDE+Rocchio validated by arxiv 2511.19349, confidence scoring kept as approximate.
    • Produced a 10-phase build plan (MASTER-PLAN.md) covering ingestion quality, privacy mode, retrieval stack, synthesis, streaming, conversation memory, delphi integration, evaluation, and embedding benchmarking.
    • Registered zap-rag in zap-projects, linked prior chat and both plan files.
    • Decisions made:

    • Stays in the zap-orcus monorepo (shared libs dependency, single-developer, matches existing pattern)
    • Develop on orcus, design for portability to dedicated container on phoebe
    • PostgreSQL + pgvector + pgvectorscale (orcus now, pg01.lan later with streaming replication)
    • Late chunking as primary embedding strategy (free contextual embeddings, no LLM cost)
    • Cross-encoder reranking promoted over LLM-based scoring
    • Frontier models for evaluation only, never production
    • All production inference via local Ollama (delphi/phoebe/titan) through LLM gateway
    • No problems encountered -- this was a design-only phase with no code execution.

      ---

      Feature: Hidden Money project -- GDoc ingestion, RAG embeddings, multi-source search

      Date: 2026-02-22 Scope: zap-writer document pipeline, pgvector, RAG search

      Changes:

    • Created ingest-gdoc-tabs.php to fetch multi-tabbed Google Docs via the Docs API v1. Required enabling the Google Docs API in the Cloud Console (project 409572928579). Reuses zap-mail's OAuth tokens (gmail_token.json).
    • Extended ingest-file.php with rechunk mode and splitOversizedBlock() to handle paragraphs that exceed chunk size limits (some Major Donor Report sections were 31K chars of continuous text).
    • Built embedding pipeline (embed-chunks.php) with auto-server selection from LLM gateway config (GATEWAY_SERVERS).
    • Installed nomic-embed-text on titan (RTX 3060) for dedicated embedding -- avoids fighting with delphi over model swapping.
    • Created rag-answer.php with 4-part answer synthesis using writer_llm() for chat and writer_search() for SearXNG.
    • Overhauled search UI in project.php with collapsible answer cards, model selector, and raw matches section.
    • Problems encountered:

    • Google Docs API not enabled: file_get_contents returned "API has not been used in project 409572928579 before or it is disabled". Fixed by enabling the Docs API in the Cloud Console.
    • Token permissions: gmail_token.json was owned by www-data with rw-------. CLI script running as jd couldn't read it. Fixed with chmod 640.
    • Embedding failures on delphi: nomic-embed-text kept getting evicted from VRAM by larger models (qwen3:30b), causing timeouts and 0.4 chunks/sec throughput. Solution: pulled nomic-embed-text to titan and updated the script to use the LLM gateway config to auto-select smaller servers first.
    • Oversized chunks: The chunkText() function split on double-newlines only. Single paragraphs exceeding 31K chars were stored as one chunk, exceeding the embedding model's 8192-token context window. Fixed by adding splitOversizedBlock() which splits by sentence boundaries, then by word boundaries as a fallback.
    • UTF-8 encoding errors: Some Google Doc text contained Windows-1252 smart quotes (byte 0x94), causing SQLSTATE[22021]: invalid byte sequence for encoding "UTF8". Fixed with mb_convert_encoding() + control character stripping.
    • Wrong Ollama hostnames: Initially tried phoebe.lan and titan.lan (Proxmox hosts), but the Ollama VMs are at ollama-phoebe.lan and ollama-titan.lan. Discovered the correct hostnames from the LLM gateway's config.php.
    • Search tab outside container div: The search tab HTML was placed after the .container closing
    , so it had no horizontal padding. Moved it inside.

    ---

    Feature: LLM gateway integration + server inventory page

    Date: 2026-02-22 Scope: Shared LLMClient, new zap-projects page

    Changes:

  • Updated shared/libs/LLMClient.php to route through the LLM gateway (https://llm.orcus.lan) instead of calling http://delphi.lan:11434 directly. All zap apps (writer, projects, etc.) now automatically get multi-server routing across delphi, phoebe, and titan.
  • Added $useGateway flag that auto-detects based on host URL. Gateway response envelope ({ok, data, server, swap}) is unwrapped transparently.
  • Added CURLOPT_SSL_VERIFYPEER => false for internal LAN HTTPS.
  • Built LLM Server Inventory page (apps/zap-projects/web/llm-servers.php) with three-column layout showing all Ollama servers. Delphi is read-only; phoebe and titan support pull/delete with streaming progress.
  • Problem fixed: test-llm-models.php was calling new LLMClient('http://delphi.lan:11434', 'qwen3:8b') with positional args, but the constructor expects an array. Changed to new LLMClient(['model' => 'qwen3:8b']).

    ---

    Bug: Chat links return "Chat not found", plans not clickable

    Date: 2026-02-19 Problem: Two UI bugs on the Cursor Hub (/projects/cursor):
  • Clicking a chat title navigated to /projects/cursor-chat-transcript.php?id=UUID which returned "Chat not found" -- the URL was wrong (cursor- prefix) and the transcript viewer still read from SQLite while all data had been migrated to PostgreSQL.
  • Plans had no click handler at all -- renderPlans() rendered
    elements with no links, so users could not view plan content.
  • Solution:

  • Fixed chat links in cursor.php to point to /projects/chat-transcript.php (correct filename). Migrated chat-transcript.php from SQLite3 to PDO/PostgreSQL (PgClient::connect()), updating table names (cursor_chats, cursor_chat_plans, cursor_chat_deep_analysis) and handling JSONB columns. Created symlink in web public directory.
  • Created new plan-viewer.php page that reads plan content from cursor_plans table (with disk fallback), renders markdown via Parsedown, and displays metadata. Wrapped plan cards and list items in tags. Created symlink in web public directory.
  • Files affected:

  • apps/zap-projects/web/cursor.php -- fixed chat hrefs, wrapped plans in links, added display: block to card CSS
  • apps/zap-projects/web/chat-transcript.php -- full SQLite-to-PostgreSQL migration
  • apps/zap-projects/web/plan-viewer.php -- new file
  • web/public/projects/chat-transcript.php -- new symlink
  • web/public/projects/plan-viewer.php -- new symlink
  • Enhancement: Lazy-load plans, sticky tabs, plan-viewer back link

    Date: 2026-02-19 Problem: Plans loaded all 449+ at once (originally capped at 100, then 500), tab selection reset to Chats on every page refresh, and the plan-viewer "All Plans" back link went to the Chats tab instead of Plans.

    Solution:

  • Added offset-based pagination to loadPlansData() with page size 50 (matching chats). loadMore() branches on currentTab. Filter/search resets planOffset.
  • Tab selection persisted to localStorage via existing prefs object (prefs.tab). On init, currentTab reads from URL ?tab= param first, then localStorage, then defaults to 'chats'. Tab buttons get correct .active class at startup.
  • Plan-viewer back link changed to /projects/cursor?tab=plans. cursor.php init reads URLSearchParams to support deep-linking.
  • Files affected:

  • apps/zap-projects/web/cursor.php -- pagination, sticky tabs, URL param support
  • apps/zap-projects/web/plan-viewer.php -- back link href
  • ---

    Bug: Plans not auto-indexed into database when created by Cursor

    Date: 2026-02-19 Problem: When Cursor creates a plan file (.plan.md), it was not being indexed into the PostgreSQL cursor_plans table. Searching for "vpn pemex" via the MCP search_all tool or the web UI returned zero results, despite the plan file existing on disk at /home/jd/.cursor/plans/vpn_vm_for_pemex_086acecd.plan.md. At the time of discovery, 445 plans existed on disk but only 416 were in the database.

    Root Cause (3 issues):

  • File watcher didn't watch plans directory: watch-transcripts.sh only watched agent-transcripts directories, not /home/jd/.cursor/plans/.
  • Single-chat mode skipped plan indexing: The watcher ran --single-chat which is guarded by if (!$singleChat) at line 695 of ingest-chat-transcripts.php, skipping all plan indexing code.
  • Search was too literal: list_plans used ILIKE '%entire query%' -- the whole multi-word string had to appear as a contiguous substring. And cursor_chats.fts_vector only indexed first_query + summary, not the full transcript, so terms like "VPN" at position 663K in a transcript were never indexed.
  • Additional issue found during fix: Plan names in the database were corrupted -- many had the full file content (up to 6KB) stored in the name column instead of the short plan name. This was caused by the upsert's ON CONFLICT clause not updating the name column, so stale data from a previous broken ingest persisted.

    Solution:

  • Added --plans-only CLI mode to ingest-chat-transcripts.php for fast plan-only indexing (~3s for 446 plans)
  • Added /home/jd/.cursor/plans/ to the inotify watch list in watch-transcripts.sh, with .plan.md events triggering --plans-only ingestion (30s debounce)
  • Added fts_vector (tsvector generated column + GIN index) to cursor_plans table
  • Expanded cursor_chats.fts_vector to include full_transcript (no size cap -- largest tsvector is ~135KB for a 5.4MB transcript, well within PostgreSQL's 1MB limit)
  • Updated MCP server and API to use plainto_tsquery FTS instead of ILIKE
  • Fixed discoverPlans() to parse YAML frontmatter name field, and fixed upsert to update name on conflict
  • Restarted systemd watcher service
  • Files affected:

  • scripts/ingest-chat-transcripts.php -- added --plans-only mode, fixed discoverPlans() name extraction, fixed upsert
  • scripts/watch-transcripts.sh -- added plans directory watching, plan event handling
  • apps/zap-projects/mcp/server.py -- switched list_plans from ILIKE to FTS
  • apps/zap-projects/web/api/plans.php -- switched search from ILIKE to FTS
  • apps/zap-projects/web/cursor.php -- fixed plans list view, added datetime display
  • Lesson learned: File watchers must watch ALL directories where user-facing content is created, not just transcript directories. Search systems should use proper full-text search, not substring matching. And upsert ON CONFLICT clauses should update ALL columns that might change, not just content/timestamps.

    ---

    Bug: Ollama qwen3:30b wedging and JSON parsing failures

    Date: 2026-02-18 Problem: During zap-writer currency scanning and fact-checking, local qwen3:30b on delphi.lan would frequently become unresponsive. The model would accept a request, then produce no output for minutes before timing out. Subsequent requests to Ollama (even simple ones) would also hang, indicating the entire Ollama server was wedged. When responses did arrive, they often contained "thinking" tokens (...) mixed into what should have been pure JSON output, causing JSON parsing failures in LLMClient::generateJSON().

    Root Cause: The qwen3 series models have a "thinking mode" that generates internal reasoning tokens before the actual response. This is triggered unpredictably and the thinking output can be very long, consuming GPU memory and time. When combined with large context windows (full chapter text), the model would exhaust GPU resources and wedge Ollama entirely.

    Solution (multi-pronged):

  • Restarted Ollama service on delphi.lan via SSH (sudo systemctl restart ollama) to clear wedged state
  • Created fix-currency-gaps.php with direct curl calls to Ollama API using /no_think prefix in prompts to suppress thinking mode
  • Increased num_predict parameter to avoid truncated responses
  • Implemented manual JSON extraction (regex for {...}) as fallback when standard parsing fails
  • For persistently problematic items, manually populated database entries based on available search results
  • Switched bibliography ingestion from LLM-based parsing to regex-based ingest-bib-direct.php for reliability
  • Files affected:

  • apps/zap-writer/bin/fix-currency-gaps.php -- created for retry logic
  • apps/zap-writer/bin/ingest-bib-direct.php -- created as LLM-free alternative
  • apps/zap-writer/bin/run-all-engines.php -- added sleep delays between LLM calls
  • Lesson learned: Local LLMs on consumer GPUs are unreliable for long batch processing. Always have: (a) a CLI runner that bypasses HTTP timeouts, (b) a non-LLM fallback for structured parsing tasks, (c) manual JSON extraction as a safety net, (d) the ability to restart the LLM server remotely.

    ---

    Bug: kimi-k2.5:cloud rate limiting (HTTP 429)

    Date: 2026-02-18 Problem: Cloud-proxied model kimi-k2.5:cloud on delphi.lan returned HTTP 429 (Too Many Requests) during batch currency scanning, causing all subsequent LLM calls to fail.

    Solution: Upgraded to Ollama paid plan, which removes rate limiting for cloud-proxied models. Also switched batch processing to local qwen3:30b to avoid cloud dependencies entirely.

    Lesson learned: Cloud-proxied models via Ollama's free tier have aggressive rate limits unsuitable for batch processing. Either use paid plan or prefer local models for batch work.

    ---

    Bug: WireGuard iptables-restore missing in Alpine LXC

    Date: 2026-02-18 Problem: When bringing up WireGuard VPN in the Alpine LXC container on phoebe.lan, wg-quick up failed with iptables-restore: command not found. The WireGuard interface was created but then immediately torn down because the firewall rules couldn't be applied.

    Solution: apk add iptables ip6tables inside the container. Alpine's minimal install doesn't include iptables by default, but WireGuard's wg-quick requires it for its routing table and firewall mark rules.

    Files affected: Container setup (not in git -- LXC on phoebe.lan)

    ---

    Bug: WireGuard resolvconf signature mismatch in Alpine LXC

    Date: 2026-02-18 Problem: After installing iptables, wg-quick up still failed because resolvconf reported a "signature mismatch" on /etc/resolv.conf and refused to update DNS settings. The WireGuard interface was again created and immediately torn down.

    Solution: Bypassed resolvconf entirely by handling DNS in the WireGuard config's PostUp/PostDown hooks:

    PostUp = cp /etc/resolv.conf /etc/resolv.conf.bak; printf "nameserver 10.0.0.243\nnameserver 8.8.8.8\n" > /etc/resolv.conf
    PostDown = cp /etc/resolv.conf.bak /etc/resolv.conf

    ---

    Bug: PIA addKey endpoint returning "Login failed!"

    Date: 2026-02-18 Problem: After generating a valid PIA auth token and WireGuard keypair, the PIA addKey endpoint at https://:1337/addKey returned {"status":"ERROR","message":"Login failed!"}. The same token worked fine for the generateToken endpoint.

    Root Cause (1): The PIA password contains a * character which was being expanded by the shell when passed through SSH heredocs. The token was acquired successfully from orcus but the addKey call was made from inside the container where the password wasn't needed -- the issue was that the addKey endpoint uses a self-signed certificate and requires PIA's own CA certificate for proper TLS validation.

    Root Cause (2): The curl call used -k (insecure) which should have worked, but PIA's server requires the --resolve flag to map the hostname to the IP address. Without --resolve, the TLS SNI doesn't match and the server rejects the request.

    Solution: Used PIA's official CA certificate (ca.rsa.4096.crt from their manual-connections repo) with --resolve flag:

    curl --cacert ca.rsa.4096.crt \
      --resolve "mexico409:1337:77.81.142.97" \
      -G --data-urlencode "pt=${TOKEN}" --data-urlencode "pubkey=${WG_PUBKEY}" \
      "https://mexico409:1337/addKey"

    Files affected: /root/scripts/vpn-connect.sh on vpn-fetch.lan container

    Lesson learned: PIA's WireGuard registration requires their specific CA cert and hostname resolution. The -k flag is not sufficient. Always use the official manual-connections repo scripts as reference.

    ---

    Bug: Bibliography ingestion too slow via LLM parsing

    Date: 2026-02-18 Problem: The initial approach to bibliography ingestion used LLM (qwen3:30b) to parse each reference from the manuscript text into structured fields (author, title, year, publisher, etc.). With 528 references, this was extremely slow (>10 seconds per reference when it worked) and frequently wedged Ollama completely.

    Solution: Created ingest-bib-direct.php which parses references using PHP regex and heuristics:

  • Detects author names (capitalised words followed by comma/period patterns)
  • Extracts years (4-digit numbers in parentheses or after commas)
  • Identifies titles (text in quotes or italics)
  • Handles URLs and DOIs
  • Completes 528 references in under 5 seconds vs estimated 90+ minutes via LLM
  • Lesson learned: LLMs should not be used for structured parsing of well-formatted text. Regex/heuristic parsers are orders of magnitude faster, more reliable, and don't consume GPU resources. Reserve LLMs for tasks that genuinely require understanding (verification, summarisation, merging).

    ---

    Bug: MCP tools not available to AI agent in SSH remote workspaces

    Date: 2026-02-17 Problem: After setting up the zap-projects MCP server and deploying config files, the AI agent in SSH remote workspaces (e.g. philoenic on orcus accessed from paris.lan) reported "I don't see a zap-projects MCP server" despite the Cursor settings UI showing "zap-projects: 7 tools enabled" with a green status dot.

    Root Cause (1 -- Windows global config path): The global MCP config on paris.lan (Windows 11) was placed at %APPDATA%\Cursor\mcp.json (C:\Users\jd\AppData\Roaming\Cursor\mcp.json). This is wrong. Cursor reads global MCP config from %USERPROFILE%\.cursor\mcp.json (C:\Users\jd\.cursor\mcp.json). The %APPDATA%\Cursor\ directory is for Cursor's internal state, not user configs.

    Root Cause (2 -- global vs project-level for SSH remotes): Even after fixing the path, the global config on the Windows client did not make tools available to the AI agent. The reason: for SSH remote workspaces, the AI agent runs on the remote machine (orcus), but the global MCP config starts the MCP server on the local machine (paris.lan). The Cursor UI (which runs on paris.lan) shows the server as connected, but the agent on orcus cannot communicate with it. The settings UI and the agent have different views of the MCP server.

    Solution: Project-level .cursor/mcp.json files at each workspace root on orcus, with a local python3 command that runs the MCP server directly on orcus (where the agent runs). This bypasses the client-server gap entirely.

    Files affected:

  • C:\Users\jd\.cursor\mcp.json (paris.lan) -- created at correct path, old one at %APPDATA% removed
  • /var/www/philoenic.com/.cursor/mcp.json -- restored with local python3 command
  • /var/www/philanthropy-planner.com/.cursor/mcp.json -- restored
  • /var/www/prospecta/.cursor/mcp.json -- restored
  • /var/www/prospecta.cc/.cursor/mcp.json -- restored
  • /APPS/quickstep/.cursor/mcp.json -- restored
  • Lesson learned: For Cursor MCP in SSH remote workspaces, always use project-level configs that run the server locally on the remote machine. Global configs on the client machine are only useful for local (non-SSH) workspaces. The settings UI can be misleading -- "enabled" and "connected" doesn't mean the agent can use the tools.

    ---

    Feature: Zap-Projects -- Chat History & Deep Analysis System

    Date: February 2026 Feature: Complete Cursor AI chat history indexing, browsing, and analysis system. Implementation:
  • Built scripts/ingest-chat-transcripts.php to scan all Cursor agent-transcripts directories, parse metadata (message counts, tool calls, apps involved, files touched, topics), generate LLM summaries via delphi.lan (qwen3:30b model)
  • Built scripts/deep-analyse-chat.php for structured JSON analysis of each chat (title, narrative summary, artifacts created, chart work, decisions made, data sources, people mentioned, unfinished work, questions raised)
  • Built scripts/watch-transcripts.sh using inotifywait to monitor transcript directories for file creates/writes, with 120-second debounce to avoid re-processing active chats
  • Created systemd service zap-chat-watcher.service for the file watcher
  • Added cron job (/30 *) for deep analysis as a safety net
  • Built web UI: chat history browser with FTS5 search, app/project filtering; transcript viewer with rendered HTML, summaries, deep analysis, plan links; AI Q&A endpoint
  • Built energystats asset indexer and browser
  • Created shared/libs/LLMClient.php as reusable Ollama client
  • SQLite database at data/chat-history.sqlite with WAL mode, FTS5 full-text search
  • 23 chats indexed, 348 plan links cross-referenced, all with summaries and deep analysis
  • Technical Decisions:
  • Used inotify (inotifywait) rather than polling for file watching -- instant detection, minimal CPU
  • 120-second debounce prevents re-analysis during active chat sessions
  • Two-step processing: fast metadata ingest first (skip LLM), then deep LLM analysis
  • FTS5 virtual table kept in sync with triggers for real-time search
  • Plan decided to migrate from SQLite to PostgreSQL (existing zap DB) for the forthcoming Project Hub, to avoid painful mid-project migration later
  • Files Created:
  • scripts/ingest-chat-transcripts.php
  • scripts/deep-analyse-chat.php
  • scripts/watch-transcripts.sh
  • scripts/index-energystats-assets.php
  • apps/zap-projects/web/chat-history.php
  • apps/zap-projects/web/chat-transcript.php
  • apps/zap-projects/web/chat-ask.php
  • apps/zap-projects/web/energystats-assets.php
  • shared/libs/LLMClient.php
  • infra/systemd/orcus_zap-chat-watcher.service
  • ---

    November 2025

    Feature: Home Page Connect Link

    Date: November 21, 2025 Feature: Added Connect page to home page Main Pages section. Implementation:
  • Added Connect page link card to home page Main Pages section
  • Positioned as first entry (before Host and Guest interfaces)
  • Marked with "Primary" badge to indicate recommended entry point
  • Description explains unified interface that auto-detects role
  • Notes that hosts can create sessions and manage recordings
  • Notes that guests can join via invite links
  • Files Changed:
  • web/public/index.php - Added Connect page link card to Main Pages section
  • User Experience:
  • Connect page now easily discoverable from home page
  • Users understand it's the unified entry point for both hosts and guests
  • Clear explanation of auto-detection and role-based functionality
  • Technical Details:
  • Connect page card uses same styling as other main page cards
  • Positioned first in grid to emphasize primary entry point status
  • Badge styling matches other primary pages (Host, Guest)
  • ---

    Feature: Chat Participants List

    Date: November 20, 2025 Feature: Converted single guest info display to participants list showing all session members. Implementation:
  • Replaced chat-sessions-guest-info div with chat-participants-list div
  • Created renderParticipants() method that displays Host and Guest with their status
  • Host always shows as "Live" (since host is on the page)
  • Guest shows "Live" or "Offline" based on connection state
  • Added truncation support: if more than 10 participants, shows "+X more" message
  • Participants list is scrollable (max-height: 200px) to handle many participants
  • Removed "Full chat β†’" link from tabs area (not functional in this context)
  • Removed redundant connection status display (now shown in participants list)
  • Files Changed:
  • web/public/connect.php - Removed "Full chat β†’" link, updated CSS for participants list
  • web/public/chat-sessions.js - Replaced guest info with participants list, added renderParticipants() method
  • Technical Details:
  • Participants array structure: { name: string, id: string, status: 'live'|'offline' }
  • Truncation limit: 10 participants (configurable via maxDisplay variable)
  • Status colors: Live = #16a34a (green), Offline = #6b7280 (grey)
  • Each participant item has border-bottom except last item
  • User Experience:
  • Clear view of who is in the session
  • Easy to see connection status at a glance
  • Ready to expand when more participants are added
  • Clean, organized list format
  • ---

    Feature: Sessions & Recordings Panel Tabs

    Date: November 20, 2025 Feature: Split Sessions & Recordings panel into separate tabs and added Chat tab. Implementation:
  • Replaced single "Sessions & Recordings" header with tab-based interface
  • Created two tabs: "Sessions" and "Recordings" (Title case, matching sidebar style)
  • Sessions tab shows list of recent sessions (default active tab)
  • Recordings tab shows recordings for selected session
  • Clicking a session automatically switches to Recordings tab and loads recordings
  • Added "Chat" tab as leftmost tab (order: Chat, Sessions, Recordings)
  • Chat panel created with blank placeholder ready for message functionality
  • Tab switching JavaScript handles all three tabs generically
  • Files Changed:
  • web/public/connect.php - Updated HTML structure, added CSS for tabs, updated JavaScript for tab switching
  • Technical Details:
  • Tabs use same CSS classes and structure as sidebar tabs for consistency
  • Tab panels show/hide based on active class
  • JavaScript uses generic tab switching that works for any number of tabs
  • Selected session ID stored globally for loading recordings when tab switches
  • User Experience:
  • Cleaner interface with organized tabs
  • Easy navigation between Sessions, Recordings, and Chat
  • Consistent tab styling across application
  • ---

    Feature: Sidebar Tab Styling Update

    Date: November 20, 2025 Feature: Changed sidebar tabs from uppercase to Title case for consistency. Implementation:
  • Removed text-transform: uppercase CSS property from .sidebar-tab class
  • Sidebar tabs now display as "Contacts", "Rooms", "Groups" instead of "CONTACTS", "ROOMS", "GROUPS"
  • Matches the Title case styling used in Sessions & Recordings tabs
  • Files Changed:
  • web/public/includes/sidebar.php - Removed text-transform: uppercase from CSS
  • User Experience:
  • Consistent tab styling across all tabs in the application
  • More readable Title case instead of all uppercase
  • ---

    Issue: Host Reconnect Button Not Working

    Date: November 20, 2025 Problem: Host "Reconnect Remotes" button was not properly cleaning up the guest video element before reinitializing WebRTC, causing reconnection to fail or behave inconsistently. Root Cause:
  • cleanupHostWebRTC() function in host-webrtc.js was closing the peer connection but not clearing the guest video element's srcObject
  • Guest's "Connect" button properly cleared hostVideo.srcObject = null and marked container as empty, but host's cleanup didn't match this behavior
  • Video element remained with old stream reference, preventing proper reconnection
  • Solution:
  • Updated cleanupHostWebRTC() to clear guest video element: guestVideo.srcObject = null
  • Added container class marking: guestVideoContainer.classList.add('empty')
  • Matches guest's cleanup behavior for consistent reconnection flow
  • Files Changed:
  • web/public/host-webrtc.js - Added guest video element cleanup to cleanupHostWebRTC() function
  • Technical Details:
  • Guest video element must be cleared before reinitializing WebRTC to prevent stale stream references
  • Container marked as empty for UI consistency
  • Peer connection cleanup already existed, only needed video element cleanup
  • User Experience:
  • Host reconnect button now works correctly, matching guest connect button behavior
  • Reconnection properly resets both peer connection and video elements
  • ---

    Issue: Connect Page Blank for Anonymous Users

    Date: November 20, 2025 Problem: When visiting /connect without being logged in and without any parameters, users saw a blank page with no guidance on what to do. Root Cause:
  • No role auto-detection logic for anonymous users
  • No helpful message explaining how to use the page
  • Video UI div hidden by default (requires active class) but wasn't getting it when no role selected
  • Solution:
  • Added auto-detection: if logged in with host permissions and no params β†’ auto-default to host
  • Added auto-detection: if session or invite params present β†’ auto-set to guest
  • Added helpful welcome message for anonymous users: "Use your Invite URL or log in"
  • Message shows two clear options with explanations
  • Video UI div now gets active class when showing welcome message
  • Files Changed:
  • web/public/connect.php - Added auto-detection logic, welcome message HTML, conditional active class
  • Technical Details:
  • Auto-detection checks: $isLoggedIn && $canHost && !$sessionId && !$inviteSlug β†’ host
  • Auto-detection checks: $sessionId || $inviteSlug β†’ guest
  • Welcome message only shown when: !$selectedRole && !$isLoggedIn && !$inviteSlug && !$sessionId
  • Video UI gets active class when showing message: ($selectedRole || (!$selectedRole && !$isLoggedIn && !$inviteSlug && !$sessionId))
  • User Experience:
  • Guests with invite links work automatically (no confusion)
  • Hosts can just visit /connect without needing ?role=host
  • Anonymous users see clear guidance on how to use the page
  • ---

    Feature: Navbar Access Control

    Date: November 20, 2025 Feature: Hide Host, Guest, Recordings, and Messages navbar links from non-host users. Implementation:
  • Added $canHost permission check to navbar component
  • Wrapped Host, Guest, Recordings, and Messages links in conditional:
  • Home, Connect, and Login remain visible to everyone
  • Admin link still only visible to admins
  • Files Changed:
  • web/public/includes/navbar.php - Added $canHost check, wrapped restricted links in conditional
  • User Experience:
  • Cleaner navigation for anonymous users (only see Home, Connect, Login)
  • Non-host users don't see links they can't use
  • Hosts see full navigation menu
  • ---

    Issue: Recording Uploads Failing with 500 Errors

    Date: November 20, 2025 Problem: Recording uploads were failing with ERR_INTERNAL_SERVER_ERROR: unexpected response code from hook endpoint (500) from tusd. Root Cause:
  • tus-hooks.php had incorrect require_once path: ../../bootstrap.php instead of ../bootstrap.php
  • PHP script was failing with fatal error before it could log anything
  • tusd received 500 response from hook endpoint, preventing uploads from completing
  • Solution:
  • Corrected bootstrap path from ../../bootstrap.php to ../bootstrap.php
  • Path now correctly resolves to /var/www/zap/web/bootstrap.php
  • Files Changed:
  • web/public/tus-hooks.php - Fixed require_once path for bootstrap.php
  • Technical Details:
  • tus-hooks.php is in /var/www/zap/web/public/
  • bootstrap.php is in /var/www/zap/web/
  • Correct relative path: ../bootstrap.php (up one level from public/ to web/)
  • User Experience:
  • Recordings now successfully upload to server
  • Post-finish hooks execute correctly for remuxing and metadata insertion
  • ---

    Issue: Guest Name Persistence and Recording Metadata

    Date: November 19, 2025 Problem: Guest name changes were not persisting across page refreshes. When user edited name from "Celine3" to "Celine34", it would revert on refresh. Also, guest name was not included in recording file names or session metadata. Root Cause:
  • Priority order was wrong: invite/URL data took precedence over sessionStorage (user edits)
  • No flag to track whether user had manually edited the name
  • Guest name not stored in database session metadata
  • Recording file names used generic "guest" instead of actual guest name
  • URL parameter would override user's edited name on page refresh
  • Solution:
  • Fixed priority order: if zap_guest_name_edited flag is set, use sessionStorage value (ignores URL/invite)
  • Added zap_guest_name_edited flag in sessionStorage to track manual edits
  • Created /api/guest-name-update.php endpoint to store guest name in sessions.metadata JSONB column
  • Updated recorder classes to accept guest name parameter and include it in file names
  • File names now: ${sessionId}-${guestNameSafe}-master.webm (sanitized for filesystem safety)
  • Guest name passed to recorder constructors when starting recording
  • URL updates when user edits, but edited name takes priority on refresh
  • Files Changed:
  • web/public/connect.php - Fixed priority logic, added edited flag tracking, added API call to store name
  • web/public/guest.js - Updated recorder classes to accept guest name, include in file names
  • web/public/api/guest-name-update.php - New endpoint to store guest name in session metadata
  • Technical Details:
  • Priority check: if (guestNameEdited && guestNameFromStorage) { use storage } else { URL > invite > storage }
  • Guest name sanitized with .replace(/[^a-zA-Z0-9_-]/g, '_') for safe filenames
  • Session metadata stored as JSONB: { "guest_name": "Celine34" }
  • Recorder instances created with: new GuestMasterRecorder(stream, sessionId, currentGuestName)
  • User Experience:
  • User edits now persist correctly across page refreshes
  • Recording files are identifiable by guest name
  • Guest name available in session metadata for future reference
  • ---

    Feature: Guest Session Header and Status Improvements

    Date: November 19, 2025 Feature: Improved guest session header layout and dynamic status messages. Implementation:
  • Changed header from "Guest Session [session-id]" to "Session: [session-id]" format
  • "Session:" is bold, session ID is normal weight (matching user request)
  • Guest name moved to right side of header with "Your name:" label
  • Guest name displays as clickable inline text with tooltip
  • Status message now dynamically reflects actual connection state
  • Status updates automatically when camera or host connection state changes
  • Connect button renamed from "Reconnect" and moved to Host Video pane controls
  • Files Changed:
  • web/public/connect.php - Restructured header HTML, moved guest name to right, updated status initialization
  • web/public/guest.js - Added updateConnectionStatus() function, replaced hardcoded status messages
  • Status Message Format:
  • "Your camera is off, remote host is not connected."
  • "Your camera is on, remote host is connected."
  • Updates in real-time as states change
  • Technical Details:
  • Status checks: guestCameraActive (boolean) and hostVideo.srcObject.getTracks().length > 0
  • Status updates called on: camera toggle, host video stream changes, WebRTC connection state changes, Centrifugo subscription
  • ---

    Feature: Guest Session Camera and Audio Controls

    Date: November 19, 2025 Feature: Added camera and audio controls to guest session page, matching host session functionality. Implementation:
  • Added camera on/off button for guest's own camera (left video pane)
  • Added mute mic button for guest's microphone (disabled until camera active)
  • Added mute audio button for host's audio feed (disabled until host stream available)
  • Added disabled camera button for host video (guest cannot control host camera)
  • All controls use same HTML structure and CSS styling as host session
  • Buttons properly enable/disable based on stream availability
  • State management integrated with existing guest.js camera toggle logic
  • Files Changed:
  • web/public/connect.php - Added video-box-controls HTML structure to guest session video boxes
  • web/public/guest.js - Added toggleGuestMicBtn variable, updateGuestMicButtonUI(), toggleGuestMic() functions, integrated mic button state with camera toggle
  • User Experience:
  • Guest session now has parity with host session for camera/audio controls
  • Clear visual feedback for button states (enabled/disabled, muted/unmuted)
  • Consistent UI/UX across host and guest interfaces
  • Technical Details:
  • Guest mic button disabled by default, enabled when camera becomes active
  • Host audio button enabled when host video stream contains audio tracks
  • Mic mute state tracked separately from camera state
  • Button UI updates use same pattern as host session (icon + text updates)
  • ---

    Feature: Guest Invite Flow and Data Display

    Date: November 18, 2025 Feature: Auto-redirect guests with invites directly to session page, display session ID and guest name from invites (even expired/used). Implementation:
  • Moved invite lookup before role selection to ensure data is available
  • Auto-set role to guest when invite parameter is present in URL
  • Skip role selection page for guests (only show for logged-in hosts)
  • Added fallback database query to retrieve invite data even if invite is expired/used
  • Session ID displays inline with "Guest Session" heading when available
  • Guest name pre-populates in input field from invite data
  • Added comprehensive console logging for debugging
  • Improved error handling for invite lookup failures
  • Files Changed:
  • web/public/connect.php - Restructured invite lookup flow, added fallback query, improved guest name/session ID display
  • Issues Fixed:
  • Guests with invites were seeing role selection page unnecessarily
  • Session ID not displaying for guests with invites
  • Guest name not pre-populating from invite data
  • Expired/used invites losing session_id and guest_name data
  • Technical Details:
  • Invite lookup now happens before role check (line 46-99)
  • Fallback query bypasses findBySlug() validation to get data for display
  • Role selection only shown if $selectedRole is null AND user is logged in with host permissions
  • Video UI automatically shows when $selectedRole = 'guest' is set
  • Session ID and guest name extracted from invite data even if invite is no longer valid
  • ---

    Issue: Sidebar Tab Spacing and Toggle Button Text

    Date: November 16, 2025 Problem: GROUPS tab was too close to the middle content area, and toggle buttons displayed incorrect text (always showing "Close" even when panels were collapsed). Root Cause:
  • Sidebar width (520px) was insufficient to provide proper spacing between tabs and middle section
  • Right padding on .sidebar-tabs container had minimal effect on visual spacing
  • Toggle button text logic was inverted - showing "Close" when panels were collapsed
  • CSS visibility rules for button text spans were not properly implemented
  • Solution:
  • Increased sidebar width from 520px to 550px in .zap-sidebar CSS rule
  • Adjusted tab container padding and gap for better visual balance
  • Fixed toggle button text to show "Open [Noun]" when collapsed, "Close [Noun]" when open
  • Added proper CSS rules for .header-toggle-text with .collapsed class logic
  • Verb now precedes noun (e.g., "Open Contacts" instead of "Contacts Open")
  • Files Changed:
  • web/public/includes/sidebar.php - Increased sidebar width, adjusted tab padding/gap
  • web/public/connect.php - Fixed toggle button HTML structure and CSS visibility rules
  • Technical Details:
  • Sidebar width is the primary control for spacing between tabs and middle content
  • Tab container padding has minimal visual effect compared to overall sidebar width
  • Toggle button state is managed via .collapsed class on button element
  • Text visibility controlled by CSS rules on .collapsed-text and .open-text spans
  • ---

    Feature: Camera Controls and UI Improvements

    Date: November 16, 2025 Feature: Implemented camera on/off controls under video panes, camera defaults to off, improved button styling and positioning. Implementation:
  • Moved camera toggle buttons from main controls to under each video pane
  • Added mute mic button under host camera (disabled until camera active)
  • Added mute audio button under guest camera (disabled until host stream available)
  • Camera button text shows action ("Camera On" when off, "Camera Off" when on)
  • Removed auto-camera activation on page load
  • Disabled queueAutoCameraActivation function
  • Improved disabled button styling for better readability
  • Moved sidebar/sessions toggle buttons to header row
  • Simplified "Logged in as" display (name only)
  • Refactored camera initialization to use DOMContentLoaded
  • Fixed syntax errors from duplicate code removal
  • Files Changed:
  • web/public/connect.php - Updated video box controls, button styling, header layout
  • web/public/recorder.js - Refactored camera initialization, removed auto-activation, fixed syntax errors
  • web/public/guest.js - Updated guest camera controls, host audio mute functionality
  • web/public/includes/sidebar.php - Updated toggle button styling
  • Issues Fixed:
  • Camera button not working (fixed initialization timing)
  • Camera auto-activating on page load (removed auto-activation code)
  • Disabled buttons not readable (improved contrast and styling)
  • Syntax error in recorder.js (removed duplicate code)
  • ---

    Feature: Invite System, Sidebar, and Sessions Panel

    Date: November 15, 2025 Feature: Implemented secure invite system, left sidebar for contacts/rooms/groups, and right-hand sessions panel with centered main content layout. Implementation:
  • Created database schema for invites, contacts, groups, and sessions tables
  • Built InvitesRepository, ContactsRepository, GroupsRepository, and SessionsRepository classes
  • Created API endpoints for invite management, contact/group listing, and session listing
  • Implemented left sidebar component with collapsible toggle (always visible)
  • Added right-hand sessions panel with collapsible toggle (always visible)
  • Integrated invite creation into host UI with contact/group dropdowns
  • Auto-create session records when invites are created with session_id
  • Centered main content panel using flexbox with proper spacing
  • Aligned both toggle buttons at same vertical level (80px from top) to match "Host Session" heading
  • Session switching from both Session Management section and right panel
  • Files Changed:
  • database/migrations/2025-11-15-0003-invites-schema.sql - Invites table schema
  • database/migrations/2025-11-15-0004-contacts-groups-sessions.sql - Contacts, groups, sessions schemas
  • database/migrations/2025-11-15-0005-invites-add-contact-group.sql - Add contact_id/group_id to invites
  • web/public/includes/sidebar.php - Left sidebar component
  • web/public/connect.php - Updated with sidebar, sessions panel, centered layout
  • web/public/api/invites-*.php - Invite management endpoints
  • web/public/api/contacts-list.php, groups-list.php, sessions-list.php - List endpoints
  • web/src/Invites/, Contacts/, Groups/, Sessions/ - Repository classes
  • Design Documents:
  • design/invite-system-design.md - Invite system architecture
  • design/sidebar-and-sessions-architecture.md - Sidebar and sessions panel design
  • ---

    Issue: Remote Video Not Showing (Track Stopping Bug)

    Date: Early November 2025 Symptoms: Remote video (host/guest) would not display on either side after WebRTC connection established. Root Cause: Code was calling stream.getTracks().forEach(track => track.stop()) in updateGuestVideo() and updateHostVideo() functions, which stopped the remote media tracks prematurely, causing the connection to fail. Solution: Removed the track stopping code from both guest.js and host-webrtc.js. Remote tracks should not be stopped by the receiving side - they are managed by the sender. Files Changed:
  • /var/www/zap/web/public/guest.js - Removed track stopping in updateHostVideo()
  • /var/www/zap/web/public/host-webrtc.js - Removed track stopping in updateGuestVideo()
  • ---

    Issue: Session ID Not Persisting

    Date: Early November 2025 Symptoms: Host page always showed "Session ID: 123" regardless of URL parameter. Root Cause: recorder.js was using hardcoded sessionId = '123' instead of reading from URL. Solution: Modified getSessionIdFromURL() in recorder.js to read session parameter from URL. Added PHP parsing in host.php to extract session ID from $_GET['session']. Files Changed:
  • /var/www/zap/web/public/recorder.js - Updated getSessionIdFromURL() to read URL parameter
  • /var/www/zap/web/public/host.php - Added PHP session ID parsing
  • ---

    Issue: Browser Cache Preventing JavaScript Updates

    Date: Early November 2025 Symptoms: JavaScript fixes not loading in browser even after code changes. Root Cause: Browser was caching JavaScript files, preventing new code from loading. Solution: Added cache-busting timestamps (?v=) to all JavaScript module imports in host.php and guest.php. Files Changed:
  • /var/www/zap/web/public/host.php - Added cache-busting to host-webrtc.js and recorder.js imports
  • /var/www/zap/web/public/guest.php - Added cache-busting to guest.js import
  • ---

    Issue: Network Routing - MacBook Unreachable

    Date: Mid November 2025 Symptoms: orcus.lan (192.168.2.32) and Windows 11 (192.168.1.17) could not ping MacBook (192.168.68.54). MacBook could ping both. Root Cause: MacBook is on a mesh network (192.168.68.0/22) while orcus.lan is on a different subnet (192.168.2.0/24). No routing configured between subnets. Solution: Established reverse SSH tunnel from MacBook to orcus.lan on port 2222. This allows orcus.lan to connect to MacBook via ssh -p 2222 juliandarley@localhost. The tunnel is maintained via systemd service on Linux and launchd on macOS. Files Changed:
  • /var/www/zap/infra/systemd/macbook-reverse-tunnel.service - Systemd service for tunnel (Linux reference)
  • /var/www/zap/infra/launchd/com.getzap.reverse-tunnel.plist - Launchd service for tunnel (macOS)
  • /var/www/zap/docs/macbook-tunnel-setup.md - Documentation for tunnel setup
  • ---

    Issue: TUS Upload Location Header Malformed

    Date: Mid November 2025 Symptoms: TUS upload Location header was malformed: https://orcus.getzap.co, zap.orcus.lan, zap.orcus.lan/files/... Root Cause: Multiple ProxyPassReverse directives across Apache proxy layers were conflicting and creating duplicate hostnames in the Location header. Solution:
  • Removed all ProxyPassReverse directives from zap-subdomain-ssl.conf and zap-tunnel-proxy.conf
  • Explicitly set X-Forwarded-Host at each proxy layer to ensure correct original hostname
  • Added regex-based Header edit* Location directive in orcus-getzap-proxy-le-ssl.conf to manually rewrite any remaining malformed Location headers
  • Files Changed:
  • /var/www/zap/infra/apache/zap-subdomain-ssl.conf - Removed ProxyPassReverse, added X-Forwarded-Host
  • /var/www/zap/infra/apache/zap-tunnel-proxy.conf - Removed ProxyPassReverse, added X-Forwarded-Host
  • /var/www/zap/infra/apache/orcus-getzap-proxy-le-ssl.conf - Added Header edit* Location directive
  • ---

    Issue: Windows 11 Camera "In Use by Another Application"

    Date: Late November 2025 Symptoms: Chrome on Windows 11 would show "Camera/microphone is in use by another application" error, even after closing all other apps. Camera worked in Edge and Chrome Incognito, but not in normal Chrome. Root Cause: Chrome Sync was re-enabling extensions from Google account even when disabled locally. One or more of the 70+ synced extensions was holding the camera handle. Solution: Disabled extension syncing in Chrome preferences (extensions and extension_settings sync settings). This prevents Chrome Sync from re-enabling the problematic extension(s). Additional Fixes:
  • Added retry mechanism with exponential backoff (max 3 retries) for getUserMedia calls
  • Added device preference for "HD Pro Webcam C920" and "Line In (Saffire 6 USB)"
  • Created diagnostic PowerShell scripts for camera troubleshooting
  • Files Changed:
  • /var/www/zap/web/public/recorder.js - Added retry mechanism and device preference
  • /var/www/zap/web/public/guest.js - Added retry mechanism and device preference
  • Chrome preferences file (via PowerShell) - Disabled extension sync
  • ---

    Issue: Guest Recording Duration Only 0.4-2 Seconds

    Date: Late November 2025 Symptoms: Guest recordings on MacBook were only 0.4-2 seconds long, even when recording for 20+ seconds. Root Cause: pollRecordingState() function was checking host recording state every 2 seconds. When host was NOT recording but guest WAS recording, it automatically called stopRecording() to sync with host. This caused guest recording to stop after ~750ms. Solution: Added isManualTestRecording flag that:
  • Gets set to true when Test Record button is clicked
  • Tells pollRecordingState() to skip auto-stop logic during manual test recording
  • Gets cleared when recording stops
  • Files Changed:
  • /var/www/zap/web/public/guest.js - Added isManualTestRecording flag and logic in pollRecordingState() and Test Record button handler
  • ---

    Issue: Fallback Recording Never Uploading

    Date: Late November 2025 Symptoms: Fallback recordings were being created and concatenated successfully, but never uploaded to server. Root Cause: Master upload was starting first and setting uploadQueue.uploading = true. When fallback file was queued for immediate upload, startUploadQueue() would see uploadQueue.uploading = true and skip processing the fallback queue. Solution: Added separate uploadQueue.fallbackUploading flag so fallback uploads can run in parallel with master uploads. startUploadQueue() now only checks uploadQueue.fallbackUploading instead of the shared uploadQueue.uploading flag. Files Changed:
  • /var/www/zap/web/public/guest.js - Added uploadQueue.fallbackUploading flag and updated startUploadQueue() logic
  • ---

    Issue: Cannot See MacBook Chrome Console Logs

    Date: Late November 2025 Symptoms: Could not access MacBook Chrome console logs for debugging, making it impossible to diagnose issues remotely. Root Cause: Browser extension was connected to Windows Chrome, not MacBook Chrome. SSH access to MacBook didn't provide browser console access. Solution: Implemented automatic console log capture in guest.php that:
  • Intercepts all console methods (log, error, warn, info, debug)
  • Queues logs and sends them to /api/debug-log.php every 2 seconds or when queue reaches 20
  • Uses sendBeacon for reliable delivery on page unload
  • Stores logs in /tmp/zap-guest-debug.log which can be read via SSH
  • Files Changed:
  • /var/www/zap/web/public/guest.php - Added automatic console log capture script
  • /var/www/zap/web/public/api/debug-log.php - Created endpoint to receive and store console logs
  • ---

    Issue: Remote Cameras Working But Playback/Upload Issues (WIP)

    Date: Late November 2025 Status: 🟑 Work In Progress Symptoms:
  • βœ… Remote cameras working (host and guest can see each other via WebRTC)
  • ❌ Uploaded host file won't play (needs investigation)
  • ❌ Guest machine uploaded nothing during multi-participant session (needs investigation)
  • Root Cause: Under investigation Solution: Pending investigation Files Changed:
  • None yet - investigation in progress
  • ---

    Issue: SSE (Server-Sent Events) Connection Instability

    Date: November 10, 2025 Symptoms: Guest's EventSource connection would open briefly, then revert to CONNECTING or CLOSED state, preventing commands from being received. PHP warnings "Undefined array key 'type'" were also observed. Root Cause:
  • SSE connection was not properly established - PHP was not sending initial data event
  • PHP-FPM/Apache environment was closing connections prematurely
  • Missing null coalescing for array keys causing PHP warnings
  • Solution:
  • Added header('Content-Type: text/event-stream; charset=utf-8') and proper SSE headers
  • Sent initial connection_established event to trigger EventSource.onopen
  • Removed fastcgi_finish_request() which was closing the connection
  • Added set_time_limit(310) and ignore_user_abort(false) for longer connections
  • Changed sleep(1) to usleep(100000) for faster responsiveness
  • Added null coalescing for $input['type'] to prevent PHP warnings
  • Added connection_aborted() checks to detect client disconnects
  • Files Changed:
  • /var/www/zap/web/public/api/session-signal.php - Improved SSE headers and connection handling
  • /var/www/zap/web/public/guest.js - Updated SSE error handling and reconnection logic
  • ---

    Issue: Apache WebSocket Proxy Rejecting Upgrade Response

    Date: November 10, 2025 Symptoms: PHP WebSocket server handshake succeeds (logs show "Handshake sent successfully"), but browser immediately disconnects with "Unexpected response code: 502/503". Apache error logs show "AH00898: Unexpected Upgrade: websocket (expecting n/a)". Root Cause: Apache's ProxyPass with http:// protocol was treating WebSocket upgrade as HTTP, not WebSocket. Apache expects ws:// protocol for WebSocket connections, but even with ws:// in ProxyPass, Apache was still rejecting the Upgrade response. Solution Attempts:
  • Enabled proxy_wstunnel module in Apache
  • Changed ProxyPass /ws http://127.0.0.1:8080/ to ProxyPass /ws ws://127.0.0.1:8080/
  • Added RewriteCond %{HTTP:Upgrade} websocket [NC] and RewriteCond %{HTTP:Connection} upgrade [NC] to RewriteRule
  • Moved WebSocket ProxyPass directives before PHP handler to prevent PHP-FPM from intercepting
  • Improved PHP WebSocket server handshake logic (non-blocking sockets, better error handling)
  • Result: Handshake succeeds on server side, but Apache still rejects Upgrade response. Recommended Solution: Use Centrifugo (Go-based WebSocket appliance) instead of PHP WebSocket server. Centrifugo runs as standalone service, no Apache proxy issues, works perfectly with PHP via HTTP API. Files Changed:
  • /var/www/zap/scripts/websocket-server.php - PHP WebSocket server implementation
  • /var/www/zap/infra/apache/zap-subdomain-ssl.conf - WebSocket proxy configuration (attempted)
  • /var/www/zap/web/public/test-ws-host.html - WebSocket test page for host
  • /var/www/zap/web/public/test-ws-guest.html - WebSocket test page for guest
  • /var/www/zap/design/zap_signalling_architecture.md - Architecture document recommending Centrifugo
  • ---

    Issue: Centrifugo v6.5.0 Configuration Format Changed

    Date: November 10, 2025 Symptoms: Centrifugo service failing to start with config errors: "error unmarshalling config: decoding failed due to the following error(s): 'admin' expected a map or struct, got \"bool\"". Service would start but not listen on port 25001. Root Cause: Centrifugo v6.5.0 uses a different configuration format than earlier versions. The design document (zap_signalling_and_centrifugo.md) was written for an older version. New format requires nested objects instead of flat keys:
  • port β†’ http_server.port
  • address β†’ http_server.address
  • admin: true β†’ admin.enabled: true
  • token_hmac_secret_key β†’ token_auth.hmac_secret_key
  • Solution: Updated config file to use v6.5.0 format with nested objects. Centrifugo now starts correctly and listens on port 25001. Files Changed:
  • /var/www/zap/infra/centrifugo/config.json - Updated to v6.5.0 format with nested objects
  • /var/www/zap/infra/systemd/orcus_centrifugo.service - Systemd service file
  • /var/www/zap/infra/apache/zap-subdomain-ssl.conf - Apache proxy configuration
  • ---

    Issue: Centrifugo Installation and Setup

    Date: November 10, 2025 Status: βœ… Resolved Summary: Installed and configured Centrifugo v6.5.0 for real-time messaging and signalling. Implementation:
  • Downloaded and installed Centrifugo v6.5.0 binary to /usr/local/bin/centrifugo
  • Created config file at /etc/centrifugo/config.json with correct v6.5.0 format
  • Created systemd service orcus_centrifugo.service (following zap project pattern with orcus_ prefix)
  • Configured Apache proxy at /centrifugo/ path to forward to http://127.0.0.1:25001/
  • Created test pages (test-centrifugo-host.html, test-centrifugo-guest.html) for connection testing
  • Verified Centrifugo is running and responding via Apache proxy
  • Configuration:
  • Port: 25001 (localhost only)
  • Namespaces: signal (no history, server-publish only) and chat (1-day history, client-publish allowed)
  • Admin interface: Enabled (insecure mode for testing)
  • API key and JWT secret: Configured (to be moved to environment variables)
  • Files Created:
  • /var/www/zap/infra/centrifugo/config.json - Centrifugo configuration
  • /var/www/zap/infra/systemd/orcus_centrifugo.service - Systemd service file
  • /var/www/zap/web/public/test-centrifugo-host.html - Host test page
  • /var/www/zap/web/public/test-centrifugo-guest.html - Guest test page
  • Files Modified:
  • /var/www/zap/infra/apache/zap-subdomain-ssl.conf - Added Centrifugo proxy configuration
  • Next Steps:
  • Create PHP CentrifugoClient class for publishing messages via HTTP API
  • Create JWT generator class for browser client authentication
  • Integrate Centrifugo into host/guest recording signalling
  • Move API key and JWT secret to environment variables
  • ---

    Issue: Centrifugo WebSocket 400 via Apache proxy

    Date: 10 November 2025 (continuing) Symptoms: Browser and CLI clients (websocat wss://orcus.getzap.co/centrifugo/connection/websocket) receive 400 Bad Request and Centrifugo logs websocket: the client is not using the websocket protocol: 'upgrade' token not found in 'Connection' header whenever the request flows through Apache. Direct connections to Centrifugo on ws://127.0.0.1:25001/connection/websocket succeed instantly. Root Cause (WIP): Apache is still stripping or overwriting the Connection: Upgrade / Upgrade: websocket headers despite enabling mod_proxy_wstunnel and experimenting with ProxyPass, RewriteRule, and explicit RequestHeader directives. Need to determine exactly where the header is lost and adjust the vhost accordingly. Investigation To Date:
  • Verified Centrifugo service works standalone using websocat ws://127.0.0.1:25001/connection/websocket
  • Tried multiple Apache configurations (direct ProxyPass, , RewriteRule, RequestHeader set, forcing Protocols http/1.1)
  • Captured Centrifugo logs and repeatedly saw missing Upgrade headers when routed via Apache
  • Collected tcpdump traces on loopback to inspect forwarded traffic (headers still absent)
  • Next Steps:
  • Continue refining Apache vhost (potentially use VirtualHost-scoped SetEnvIf Upgrade websocket patterns)
  • Consider temporary bypass or dedicated upstream proxy until Apache forwards headers cleanly
  • ---

    Issue: Centrifugo integration into host/guest signalling (Phase 2 continuing)

    Date: November 10, 2025 Status: 🟑 Work In Progress Summary: Completed the PHP + browser wiring so host and guest pages connect to Centrifugo via JWTs and exchange recording commands in real time. Still need to migrate WebRTC offer/answer/ICE traffic and retire the legacy session-signal.php endpoints after full regression tests.

    Changes:

  • web/public/recorder.js: Added Centrifugo client bootstrap, token caching, subscription resilience, and record:start / record:stop publishing via /api/centrifugo-publish.php
  • web/public/guest.js: Replaced custom /ws listener with Centrifugo subscription, status publishing, and start/stop handling
  • web/public/api/centrifugo-publish.php: Accepted {channel,data} payloads for generic publishes and improved error handling
  • web/src/Realtime/CentrifugoClient.php: Allowed extra fields when publishing start/stop/chat events so timing metadata propagates
  • web/public/host.php & web/public/guest.php: Added local centrifuge.min.js include to guarantee client availability during testing
  • Next Steps:

  • Move WebRTC offer/answer/ICE signalling onto Centrifugo so /api/session-signal.php can be deprecated
  • Exercise full host↔guest recording flow over Centrifugo and confirm uploads stay stable
  • Harden secrets management (ensure systemd env vars override placeholders everywhere)
  • ---

    Issue: Move WebRTC signalling onto Centrifugo

    Date: November 10, 2025 Status: βœ… Completed Summary: Replaced the polling/SSE bridge with Centrifugo for SDP and ICE exchange. Signal namespace history (20 messages / 2 minutes) ensures late subscribers can replay the most recent offer/answer. Host/guest pages now publish webrtc:offer, webrtc:answer, and webrtc:ice events; session-signal.php remains only as a fallback pending regression tests.

    Changes:

  • infra/centrifugo/config.json: enabled history for the signal namespace (size 20, TTL 2 minutes)
  • web/public/recorder.js: tracked offsets, replayed history, and routed guest WebRTC publications into host-webrtc.js
  • web/public/host-webrtc.js: removed polling, exposed processGuestAnswer/processGuestIceCandidate, and published offer/ICE via Centrifugo
  • web/public/guest.js: consumed Centrifugo history, ignored self-published events, and published answer/ICE via publishGuestSignal
  • web/public/api/centrifugo-publish.php & web/src/Realtime/CentrifugoClient.php: accepted generic payloads and allowed extra metadata on publish helpers
  • Documentation updates (README.md, CHANGELOG.md) describing the migration
  • Next Steps:

  • Perform full Win11 ↔ MacBook recording regression now that WebRTC signalling is pub/sub based
  • Remove /api/session-signal.php and associated storage once field tests confirm stability
  • Automate Centrifugo service reload/deploy so config history changes propagate safely
  • ---

    Issue: Fallback Recordings Succeed but Host Audio + Master Streams Missing

    Date: November 11, 2025 Status: 🟑 Work In Progress Symptoms:
  • βœ… Fallback recordings generated for both host (Windows 11) and guest (MacBook)
  • ❌ Host fallback WebM plays with no audio track
  • ❌ No master recordings produced for either participant (expected *-master.webm absent)
  • Investigation To Date:
  • Confirmed MediaRecorder fallback path invoked on both sides via debug logs
  • Verified TUS uploads completed for fallback files only; master queue never started
  • Observed new Centrifugo-driven toggle flow calling cleanupHostWebRTC() when camera hidden; need to confirm this does not drop master recorder tracks
  • Next Steps:
  • Inspect master recorder pipeline in recorder.js to ensure tracks rebind after camera toggle
  • Capture full debug log timeline for record:start / record:stop to confirm publish payloads include mode: master
  • Compare guest vs host MediaRecorder constraints, focusing on Windows audio device selection and track cloning order
  • ---

    Issue: Manual reconnect flow required full page reload

    Date: November 11, 2025 Status: βœ… Resolved Symptoms: Host/guest needed to refresh the entire page to recover WebRTC or Centrifugo disconnects after network hiccups. Root Cause: No UI hook existed to reset the Centrifugo subscription + RTCPeerConnection, so stale state persisted until a full reload. Solution:
  • Added πŸ”„ Reconnect buttons on host (reconnect-remotes) and guest (reconnect-remotes) pages.
  • Buttons call new helpers (attemptHostReconnect, attemptGuestReconnection) that reset Centrifugo token/subscription, reinitialise WebRTC, and optionally notify the remote participant via reconnect:request.
  • reconnect:request publications now trigger the opposite side to retry automatically; host logs reconnect attempts via HOST_RECONNECT_*, guest uses debugLog.
  • Enabled allow_history_for_client in signal namespace to prevent Centrifugo history permission errors during recovery.
  • Files Changed:
  • web/public/host.php
  • web/public/guest.php
  • web/public/recorder.js
  • web/public/guest.js
  • infra/centrifugo/config.json
  • ---

    Issue: mkcert CA warning showing on public domain

    Date: November 12, 2025 Status: βœ… Resolved Symptoms: mkcert CA installation warning appeared on orcus.getzap.co (public domain) even though it uses Let's Encrypt and doesn't need client cert installation. Root Cause: Architecture misunderstanding - orcus.getzap.co is proxied through du2 (Let's Encrypt) β†’ SSH tunnel β†’ orcus.lan β†’ zap.orcus.lan vhost. The tunnel proxy sets Host: zap.orcus.lan for vhost matching, so PHP code checking HTTP_HOST saw zap.orcus.lan even when accessed via public domain. Solution: Updated detection logic to check X-Forwarded-Host header (set by du2 proxy, preserved by tunnel proxy) to detect public domain access. Warning now only shows for direct access to zap.orcus.lan (local domain, mkcert), not for orcus.getzap.co (public domain, Let's Encrypt). Files Changed:
  • web/public/index.php
  • README.md (added detailed architecture documentation)
  • ---

    Issue: No common navigation across main pages

    Date: November 12, 2025 Status: βœ… Resolved Symptoms: Main pages (home, host, guest, recordings) had no common navigation, making it difficult to move between pages. Root Cause: Each page was standalone with no shared navigation component. Solution: Created reusable navbar component (includes/navbar.php) with sticky positioning, responsive design (hamburger menu on small devices, full menu on larger), and active page highlighting. Integrated into all main pages via PHP include. Also created comprehensive home page with organized sections for main pages, test pages, and system status. Files Changed:
  • web/public/includes/navbar.php (new)
  • web/public/index.php (home page redesign)
  • web/public/host.php
  • web/public/guest.php
  • web/public/recordings.php
  • ---

    Issue: Messaging data lacked durable server-side store

    Date: November 14, 2025 Status: βœ… Resolved Symptoms: Chat prototypes published messages directly to Centrifugo with no persistence, so reconnects lost context and nothing enforced idempotency or read receipts. Solution: Provisioned PostgreSQL zap database (role zap_user) plus migration database/migrations/2025-11-12-0001-chat-schema.sql (rooms, members, messages, receipts, attachments). Added PDO connection helper and ChatRepository for room bootstrap, history, and read receipts. Files Changed:
  • database/migrations/2025-11-12-0001-chat-schema.sql
  • web/src/Database/Connection.php
  • web/src/Chat/ChatRepository.php
  • web/bootstrap.php
  • ---

    Issue: Chat API needed durable publish path

    Date: November 14, 2025 Status: βœ… Resolved Symptoms: Browsers could not send/receive chat data with persistence; no endpoints existed to bridge Centrifugo with the new Postgres schema. Solution: Implemented REST endpoints (chat-send, chat-history, chat-read, chat-rooms) that validate input, enforce idempotent UUIDs, and publish canonical payloads via CentrifugoClient. Updated README + design docs with migration + env instructions. Files Changed:
  • web/public/api/chat-send.php
  • web/public/api/chat-history.php
  • web/public/api/chat-read.php
  • web/public/api/chat-rooms.php
  • design/zap_messaging_foundation.md
  • README.md
  • CHANGELOG.md
  • web/public/messages.php
  • web/public/messages.js
  • ---

    Lessons Learned

  • Never stop remote media tracks - Remote tracks are managed by the sender, not the receiver
  • Always use cache-busting for JavaScript files during development
  • Reverse SSH tunnels are essential for debugging remote devices on different subnets
  • Chrome Sync can cause issues - Disable extension sync if extensions are causing problems
  • Polling can interfere with manual operations - Use flags to prevent polling from interfering with manual actions
  • Parallel uploads need separate flags - Don't use a shared flag for parallel operations
  • Console log capture is essential for remote debugging - Implement early in development
  • ---

    Future Improvements

  • Consider using WebRTC data channels for state synchronization instead of polling
  • Implement proper error recovery for failed uploads
  • Add retry logic for failed uploads
  • Consider using IndexedDB instead of OPFS for better browser support
  • Add metrics/monitoring for recording quality and upload success rates
  • ---

    Issue: Messaging API 500 Errors - Missing Composer Dependencies

    Date: November 15, 2025 Symptoms: /api/chat-rooms.php and other messaging endpoints returning 500 errors with "Class 'Dotenv\Dotenv' not found" Root Cause:
  • Composer dependencies were not installed because php-intl extension was missing (required by Composer's Symfony components)
  • .env file had permissions 600 (readable only by owner jd), but PHP-FPM runs as www-data, so it couldn't read database credentials
  • Solution:
  • Installed php8.3-intl extension via apt-get
  • Ran composer install in web/ directory to install dependencies
  • Changed .env file permissions to 640 and set group to www-data: chmod 640 env/.env && chgrp www-data env/.env
  • Regenerated Composer autoloader: composer dump-autoload
  • Restarted PHP-FPM to load new extension
  • Files Changed:
  • /var/www/zap/web/composer.lock - Generated by Composer install
  • /var/www/zap/env/.env - Permissions changed (not in git)
  • ---

    Issue: Duplicate Messages in Messaging UI

    Date: November 15, 2025 Symptoms: When sending a message, the originator sees it twice in the chat interface Root Cause: Message was being added to the UI twice:
  • Once from the API response in sendMessage() function
  • Once from Centrifugo subscription handler when the message was published back
  • The deduplication check only checked serverMessageId, but there was a race condition where the message could arrive from Centrifugo before the API response was processed, or vice versa Solution: Added deduplication checks in both places, checking both serverMessageId and clientMessageId:
  • In sendMessage(): Check for duplicates before adding message from API response
  • In Centrifugo subscription handler: Check for duplicates by both IDs before adding
  • Files Changed:
  • /var/www/zap/web/public/messages.js - Added deduplication logic in sendMessage() and subscription handler
  • ---

    Issue: Messaging UI Not Restoring State on Page Refresh

    Date: November 15, 2025 Symptoms: After refreshing the page, the left panel showed the last active room, but the right panel showed "No room selected" until manually clicking the room again Root Cause: Rooms were being loaded from localStorage and rendered, but no automatic selection of the most recently active room was happening on page load Solution: Added auto-selection logic on page load that finds the most recently active room (by lastActive timestamp) and automatically calls selectRoom() to restore the UI state, load history, and connect to Centrifugo Files Changed:
  • /var/www/zap/web/public/messages.js - Added auto-selection logic after loadRooms() and renderRooms()
  • ---

    Issue: Unnecessary use Throwable; Statements in API Files

    Date: November 15, 2025 Symptoms: PHP warnings: "The use statement with non-compound name 'Throwable' has no effect" Root Cause: Throwable is a built-in PHP interface, not a class that needs to be imported Solution: Removed use Throwable; statements from all API files Files Changed:
  • /var/www/zap/web/public/api/chat-rooms.php
  • /var/www/zap/web/public/api/chat-send.php
  • /var/www/zap/web/public/api/chat-read.php
  • /var/www/zap/web/public/api/chat-history.php
  • ---

    Feature: Authentication and Authorization Foundation (WIP)

    Date: November 15, 2025 Implementation: Built complete authentication system foundation for user login and role-based permissions Components Created:
  • PostgreSQL migrations for users and user_permissions tables
  • Session helper class for PHP session management (login, logout, permission checks)
  • UsersRepository for user lookup and permission queries
  • API endpoints: /api/auth-login.php, /api/auth-logout.php, /api/auth-check.php
  • Test user creation script (scripts/create-test-user.php)
  • Browser test page (/test-auth.html)
  • Database Schema:
  • users table: email (unique), password_hash, display_name, timestamps
  • user_permissions table: can_host, can_manage_recordings, granted_by, notes
  • Test User: test@example.com / test123 (can_host: true, can_manage_recordings: true) Next Steps: Create /login.php page and /connect page with authorization checks Files Created:
  • /var/www/zap/database/migrations/2025-11-15-0002-users-auth-schema.sql
  • /var/www/zap/web/src/Auth/Session.php
  • /var/www/zap/web/src/Auth/UsersRepository.php
  • /var/www/zap/web/public/api/auth-login.php
  • /var/www/zap/web/public/api/auth-logout.php
  • /var/www/zap/web/public/api/auth-check.php
  • /var/www/zap/scripts/create-test-user.php
  • /var/www/zap/web/public/test-auth.html
  • /var/www/zap/docs/testing-auth-and-migration.md
  • ---

    Feature: Dual-Write Migration SQLite β†’ PostgreSQL (WIP)

    Date: November 15, 2025 Implementation: Started migration from SQLite to PostgreSQL for recordings metadata Strategy: Dual-write approach - write to both databases simultaneously during transition period Components Created:
  • PostgreSQL migration for recordings table (2025-11-15-0001-recordings-schema.sql)
  • RecordingsRepository class for PostgreSQL operations
  • Updated tus-hooks.php to write to both SQLite and PostgreSQL
  • Migration Phases:
  • βœ… Phase 1: Dual-write (both databases) - ACTIVE
  • ⏳ Phase 2: Migrate existing SQLite data to PostgreSQL (script needed)
  • ⏳ Phase 3: Switch reads from SQLite to PostgreSQL
  • ⏳ Phase 4: Remove SQLite code
  • Benefits: Single database, better concurrency, easier backups, can join recordings with chat rooms by session_id Safety Features:
  • Idempotent writes (won't duplicate if hook called twice)
  • Graceful degradation: PostgreSQL errors don't break SQLite writes
  • Both databases remain functional during transition
  • Files Created/Modified:
  • /var/www/zap/database/migrations/2025-11-15-0001-recordings-schema.sql
  • /var/www/zap/web/src/Recordings/RecordingsRepository.php
  • /var/www/zap/web/public/tus-hooks.php - Added PostgreSQL dual-write
  • ---

    Feature: Admin Panel and Password Management System

    Date: November 15, 2025 Status: βœ… Complete Description: Implemented comprehensive admin panel, user registration, password management with one-time codes, and improved user experience with navbar dropdown menu.

    Features Implemented:

  • Admin panel (/admin.php) for user management - create users, assign permissions, view all users
  • User registration (/register.php) with auto-login
  • Password management: temporary passwords, one-time login codes (12-hour validity), password reset
  • Account page (/account.php) for user profile and password management
  • Connect page improvements: auto-role selection, guest name support, user info display
  • Navbar user dropdown with account, change role, and logout options
  • Email helper class (logs to file, ready for SMTP integration)
  • Database Changes:

  • Added password management columns to users table: password_change_required, temporary_password_hash, one_time_code, one_time_code_expires_at, is_admin
  • Migration script: database/migrations/2025-01-20-0001-password-management.sql
  • API Endpoints Created:

  • /api/admin-users.php - Admin user management (list, create, update permissions)
  • /api/auth-register.php - User registration
  • /api/auth-reset-password.php - Password reset with one-time code generation
  • /api/auth-set-password.php - Password change
  • Repository Updates:

  • UsersRepository - Added methods for password management, one-time codes, admin checks, user creation with temp passwords
  • Session - Added admin and password change tracking
  • Files Created:

  • web/public/admin.php - Admin panel UI
  • web/public/account.php - Account settings page
  • web/public/register.php - Registration page
  • web/public/logout.php - Logout page
  • web/public/api/admin-users.php - Admin API
  • web/public/api/auth-register.php - Registration API
  • web/public/api/auth-reset-password.php - Password reset API
  • web/public/api/auth-set-password.php - Password change API
  • web/src/Utils/EmailHelper.php - Email helper class
  • database/migrations/2025-01-20-0001-password-management.sql - Password management schema
  • Files Modified:

  • web/public/login.php - Added one-time code support, password change modal, forgot password
  • web/public/connect.php - Added role auto-selection, guest name support, user info display
  • web/public/includes/navbar.php - Added user dropdown menu with account, change role, logout
  • web/public/api/auth-login.php - Added one-time code and password change handling
  • web/src/Auth/Session.php - Added admin and password change tracking
  • web/src/Auth/UsersRepository.php - Added password management methods
  • User Experience Improvements:

  • Replaced "Change Role" button with logged-in user display text
  • User icon in navbar with dropdown menu
  • Auto-role selection (host if logged in with permissions, guest otherwise)
  • Guest name support via URL parameter and sessionStorage
  • Admin panel with temporary password display (show/hide, copy button)