Undrift undrift
Download template
User Guide
Developer Docs

Undrift — User Guide

What it is, how to set it up, and how to use every feature during your meeting.

1. What Is Undrift?

Undrift runs in a browser tab during your meeting. It listens to what's being said — or you can type observations — compares the conversation to your agenda, and alerts you when things start to drift. When a topic goes off-track, it suggests a diplomatic redirect you can say out loud, so you're never the bad cop.

Three phases: Set up your agenda before the meeting → facilitate with live AI assistance during → download a scored report after.

Undrift's Terms of Service and Privacy Policy are available for review.

What it does not do

  • It does not record or store your meeting audio — speech is processed locally by the browser
  • It does not save session data between sessions — each session starts fresh
  • It does not send audio to an external server — only text transcripts are sent to the AI

Does Undrift work for remote or hybrid meetings?

Not yet. This Open Beta (v2.0.0) is designed specifically for in-person meetings where everyone is physically in the same room.

We're launching in-person first because it lets us perfect the core experience — real-time drift detection, smart redirects, and session analytics — without the added complexity of screen sharing, virtual participants, or audio routing.

What's next: If the in-person beta is successful, remote and hybrid support will be our immediate next priority. We'll begin development as soon as we hit our beta success metrics.

2. Session Setup

Open Undrift in Chrome or Edge. You'll land on the Session Setup screen with three ways to enter your agenda:

1

Choose your agenda entry method

Build form — type agenda items directly, set topic, minutes, and type.
Upload agenda — download the .md template, fill it in, upload the completed file. Accepts .md, .txt, .csv, .json. Note: .docx files cannot be uploaded (browsers cannot read binary Word files without external libraries).
Paste & parse — paste any agenda format and AI extracts the structure.

2

Fill in meeting details

Meeting title and agenda items are required. Date, duration, audience, facilitator name, and desired outcomes are optional but improve the report and redirect suggestions.

3

Pick a date

Click the date field or the calendar icon to open a date picker. Today is pre-selected. The date always displays as DD-MMM-YYYY (e.g. 07-Apr-2026). Dates imported from uploaded or pasted agendas are automatically converted to this format.

4

Set sensitivity

Gentle — flags only clear drift. Balanced — recommended for most meetings. Vigilant — flags early and often for tight, high-stakes sessions.

5

Launch

Click Launch session to review your configuration, then Enter meeting room to start the session timer.

Agenda item types

TypeUse when…
DiscussionOpen conversation without a required decision
DecisionThe group needs to reach a firm conclusion
Info shareOne person presents, minimal back-and-forth
WorkshopCollaborative working time — exercises, whiteboarding
Q&AStructured question and answer
BreakScheduled pause

3. The Dashboard

Top bar

ElementWhat it does
Meeting titleYour meeting name and audience
Status pillLiveWrapping upOver time
Scope sliderAdjust AI sensitivity live — Gentle / Balanced / Vigilant
Elapsed badgeTotal session time running
ClockCurrent wall clock time
HelpOpens this user guide in a new tab
End sessionStops the timer and generates your end-of-meeting report

Status bar

The color-coded track shows all agenda items proportional to their time allocation. Blue pulsing = active, Green = done, Gray = upcoming. The row below shows current item name, counter, on-track status, type, and a live countdown that turns amber at 80% and red when over.

Left sidebar — Agenda

Each item shows a timing row: pending items show allocated minutes in gray; the active item shows live elapsed time shifting blue → amber → red; completed items show actual vs. allocated time in green (on time) or red with overage. Click any item to jump to it.

Center — Live feed

Card typeColorMeaning
FlagAmber left borderAI or manual flag — discussion is drifting
Drift detectedRed left borderSignificant drift — redirect suggested
Time alertPurple left borderItem or session is at time risk
Suggested redirectBlue left borderAI-generated script you can say out loud
NoteGray left borderManual observation or system message

Each card has a dismiss (×) button. Flag/Drift cards have Park it. Redirect cards have Copy. Use Filter to show one card type. The speaker icon enables audio cues.

Right panel — Buckets

  • Actions — tasks to complete after the meeting
  • Parking — valid topics that are off-agenda for today
  • Notes — anything else worth capturing

Type into the input at the bottom of each tab and press Enter to add. All buckets appear in the end-of-meeting report.

4. Action Buttons

The action bar scrolls horizontally between the feed and input bar. Each button gives a subtle press animation to confirm it registered.

Mark complete

Stamps the active item done with an exact timestamp and advances to the next item. Keeps timing data accurate for the report.

Flag topic

Manually raises a flag — logged in the feed and the report scorecard. Use when you spot drift before the AI does.

Parking lot

Opens a capture sheet. Describe the off-agenda topic and it saves to the Parking tab and appears in the report.

Log action

Opens a capture sheet. Describe the action item (add an owner if known) and it saves to the Actions tab.

Time check

Pops a summary: item elapsed vs. allocated, session elapsed vs. total, and items remaining — all color-coded.

Suggest redirect

Asks the AI for a diplomatic redirect script based on the current transcript. Use Copy to grab it.

Add note

Opens a capture sheet for a freeform facilitator note, saved to the Notes tab.

Capture sheet shortcut: Cmd/Ctrl+Enter saves without reaching for the mouse.

5. Microphone & Transcript

Click the microphone button or press M (when not in an input field) to start listening. A red pulsing bar appears when the mic is active.

Browser requirement: The microphone only works in Chrome or Edge. In Firefox or Safari, use the text input instead — it routes through the same AI pipeline.

Transcript panel

Click the speech bubble icon in the input bar to open the live transcript. It auto-opens on first mic activation and shows every finalized utterance with a timestamp and running word count.

Language switching

The dropdown in the listening bar lets you switch between EN, ES, FR, DE, PT, ZH, and JA mid-session.

Using Undrift with Zoom, Teams, or Skype

Undrift listens through your microphone — it doesn't tap into video call software directly. For fully remote meetings where you want to capture all participants:

  1. Install a virtual audio device: Loopback (Mac) or VB-Audio Virtual Cable (Windows)
  2. Route your system audio (the mixed call audio) to the virtual mic
  3. In Chrome, select the virtual mic as the microphone input for Undrift

Without this setup, Undrift only hears your microphone — useful for in-person or hybrid rooms where a room mic captures everyone.

What gets sent to the AI

Only text — not audio. The last ~600 characters of the transcript buffer are sent with each analysis request, along with current agenda item state and timing data.

6. AI Analysis

Undrift automatically analyzes the transcript against your agenda on a rolling basis. Frequency depends on sensitivity:

SettingBehavior
GentleOnly flags clear, unambiguous off-topic discussion
BalancedFlags moderate drift after a noticeable stretch — recommended
VigilantFlags early and often — best for tight, high-stakes sessions

Change sensitivity at any time with the Scope slider in the top bar. The Suggest redirect button triggers an on-demand suggestion regardless of the automatic schedule.

7. Report

Click End session and confirm. The report appears immediately. At the top of the report, the co-facilitator summarizes the session — below that summary, click .md to download a markdown version (pastes cleanly into Notion, email, Slack, or any doc tool) or .pdf (coming soon). Click New session to reload and start fresh. Download your report first — session data is not persisted.

SectionContents
Score badge0–100 with letter grade: Excellent / Good / Fair / Needs work
ScorecardItems completed, time on-agenda %, session duration vs. planned, flags raised
Agenda recapEach item with actual vs. allocated time and completion status
Desired outcomesYour pre-meeting goals for reference
Action itemsEverything logged to the Actions bucket
Parking lotAll parked topics
Facilitator notesAll notes added during the session

Click Download .md to save a markdown version — pastes cleanly into Notion, email, Slack, or any doc tool. Click New session to reload and start fresh. Download your report first — session data is not persisted.

8. Tips for Best Results

  • Run Undrift in a separate window or on a second screen
  • Balanced sensitivity works for most meetings — switch to Vigilant for tight, high-stakes agendas
  • Even without the mic, type key phrases into the text input periodically to give the AI context
  • Use Parking lot generously — the report lists everything, and parked topics often become next meeting's agenda
  • The Suggest redirect button is most effective mid-drift, not after the topic has already moved on
  • Mark items complete as you go — the report scorecard depends on accurate timestamps
  • Download your report immediately after ending — data is not saved between sessions

9. What's Changed

VersionChange
v2.3.0-betaFAQ page — New standalone faq.html at https://undrift.io/faq; 7 sections, 29 questions with full content; two-level accordion (sections collapse/expand independently, questions single-open globally); all sections start collapsed; template download links wire directly to downloadTemplateMd() / downloadTemplate() via mirrored inline script block.
v2.3.0-betaNav standardization — All nav bars across index.html and faq.html are now identical: Logo → How it works → Features → Who it's for → FAQ → Start free. FAQ link absolute URL; active state on faq.html; anchor links use root-relative cross-page format.
v2.3.0-betaScorecard card colors fixed.sc-card.sc-ok, .sc-card.sc-warn, .sc-card.sc-over CSS rules added to undrift-styles.css; green/amber/red top-border now renders correctly. Also fixed a malformed .sc-card.sc-card.sc-card.sc-label chained selector preventing .sc-label from being styled.
v2.2.0-betaMobile responsive layout — Full accordion-based mobile UI at max-width 767px. Now Bar, hamburger menu, animated sections, live data mirroring, mic toggle with permission flow, tap-to-capture transcript actions. Desktop layout completely unaffected.
v2.2.0-betaDead code cleanup — Removed 25 orphaned CSS classes, 5 orphaned JS functions, stale comment placeholders. App reduced ~6KB. All mobile colors use CSS variables for automatic dark mode support.
v2.1.0-betaLegal pages refactored to static HTML — Converted Terms and Privacy from JS-rendered to fully static HTML; eliminated 5 files (~1,009 lines); removed -landing.html variants; single unified page works everywhere; faster loading, better SEO, simpler maintenance.
v2.1.0-betaDuration field validation — Setup screen now enforces 60-minute max on total duration field; real-time error toast appears on exceed; blocks Preflight Check button when invalid; yellow danger styling; positioned beside field.
v2.1.0-betaTimecheck toast positioning fix — Moved from bottom: 70px; left: 20px to bottom: 100px; left: 300px to clear dashboard chrome; yellow styling matches brand; button active state syncs with toast visibility.
v2.1.0-betaLogo file corruption fix — Renamed undrift_logo.pngundrift-logo.png (hyphen fixes MIME type); updated all references across 9 files; standardized logo size to 32×32px everywhere; removed subtitles from legal page headers.
v2.1.0-betaCSP Google Fonts fix — Added https://fonts.googleapis.com to style-src and https://fonts.gstatic.com to font-src in vercel.json CSP headers.
v2.1.0-betaSend button dark mode fix — Added explicit dark mode override to keep send button bright brand blue (#1A6BFF) instead of washed-out light blue in dark theme.
v2.1.0-betaHeader button visited state fix — Help and Feedback buttons now preserve brand blue color on :visited:hover instead of reverting to browser default purple.
v2.1.0-betaLegal metadata updated — Effective Date changed from April 10 → April 22, 2026; Version updated to 2.0.0-beta.0; Open Beta Addendum rewritten for public open beta (removed confidentiality, invitation requirements, closed beta language).
v2.0.0-betaOpen Beta launch — Public open beta release on April 22, 2026. Free access, no gate. Success metrics: 100+ users, 50+ sessions, NPS ≥40, 99%+ uptime.
v1.13.3 BetaReport polish — Report topbar replaced with inline setup-topbar pattern; Session Health label moved above score; theme toggle added; grade label color now matches score (green/blue/amber/red); all badge text changed to white for better contrast.
v1.11 BetaReport export to PDF — Download .pdf button wired up in report topbar; downloadPDF() calls window.print(); @media print CSS hides beta ribbon, topbar, co-facilitator pill, nav, and dashboard chrome; report body goes full-width on print.
v1.11 BetaOne-click transcript capture — Inline capture buttons on every transcript line (hover-reveal); Flag, Action item, Decision, Parking lot, Note; pill-style matching action bar; pre-fills capture modal with transcript text; CAPTURE_CONFIGS lookup eliminates SVG injection in onclick; hover highlight on transcript lines.
v1.11 BetaAction bar trimmed — Reduced to 4 facilitation-focused buttons: Mark complete, Time check, Suggest redirect, Add note. Capture actions moved to transcript line buttons.
v1.11 BetaSecurity hardening — CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy headers added to vercel.json; CORS restricted from wildcard to undrift.io only; req.body validated and sanitised in proxy before forwarding to Anthropic; maxlength attributes added to all user text inputs.
v1.11 Betaapp.undrift.io routing fix — Host-based rewrite replaced with redirects entry; app.undrift.io now correctly redirects to undrift.io/app.
v1.11 BetaCONTRIBUTING.md — Added to repo root; covers branch/PR workflow, commit format, build/deploy steps, version bump checklist, code style, file naming, bug reporting, and GitHub Issues label reference.
v1.10 BetaKit waitlist integration — double opt-in signup wired into both landing pages; Kit form fields replace Mailchimp convention; email confirmation toast added (?confirmed=1 → green glass toast, auto-dismisses 5s, URL cleaned via history.replaceState).
v1.10 BetaWaitlist confirmation pageundrift-kit-confirmation.html served at /confirmed; nav and footer match index.html; Kit welcome email produced for Waitlist Welcome sequence.
v1.10 BetaLogo standardizedundrift-logo.png at absolute URL replaces all inline SVG logo marks across every page and the undrift-report.js dynamic template literal; orphaned CSS gradient backgrounds and SVG rules removed.
v1.9 BetaSensitivity fix — slider ID mismatch fully resolved; SENS_LABELS declared and used throughout; session now launches correctly with all agenda data and timers.
v1.9 BetaLegal pages — Terms and Privacy now available in two skins: in-app (light) at /terms and /privacy, and dark landing skin at /terms/landing and /privacy/landing. Content is managed from a single source — both skins always match.
v1.9 BetaPost-launch landing pageundrift-landing-launch.html added at undrift.io/launch. Features accordion with 6 unique interactive mockup panels. Problem stats redesigned as branded cards with gradient eyebrow stripes.
v1.9 BetaLanding page polish — ticker upgraded to vivid gradient borders; quote section repositioned and restyled; section order updated on both landing pages; typography bumped throughout; sourced problem stats.
v1.9 BetaReport footer — standardized to v1.9 Beta · © Undrift LLC 2026 · April 11, 2026 · Terms · Privacy with "Generated by Undrift.io" bold and centered below.
v1.8 BetaBug fixes — onboarding welcome/ToS gate restored; launch button fixed (was silently failing due to a slider ID mismatch); landing page content below the hero now visible.
v1.8 BetaReport download — download buttons moved into the co-facilitator pill at the top of the report. A divider separates the session summary from the download row. .md downloads immediately; .pdf is coming soon.
v1.8 BetaLogo & favicon — Undrift logo now appears at all brand locations in the app and as the browser tab icon.
v1.8 BetaDomain — Undrift is now live at undrift.io (landing page) and app.undrift.io (app).
v1.8 BetaLegal pages added — undrift-terms.html (Terms of Service) and undrift-privacy.html (Privacy Policy) linked from app footer.
v1.8 BetaAgenda template redesigned — cleaner format, inline hints for all fields, realistic example items. Parser aliases: MEETING_DURATION_MINUTES and SENSITIVITY accepted alongside original keys.
v1.8 BetaDynamic footers — all HTML doc pages now show live version and date via UNDRIFT_VERSION. Copyright updated to © Undrift 2026.
v1.8 BetaAudio compatibility — Zoom, Teams, and Skype work via virtual audio device (Loopback on Mac, VB-Audio on Windows). See Section 5 for setup steps.
v1.7 BetaCo-facilitator voice — contextual Undrift messages appear after agenda upload, paste & parse, on the preflight card, and at the top of the post-meeting report.
v1.7 BetaWelcome / ToS gate — first-visit screen with co-facilitator greeting and clickwrap ToS acceptance. Stored in localStorage.
v1.7 BetaPreflight check — session launch screen renamed and updated; co-facilitator message injected before entering the meeting room.

For full version history (v1.0 Alpha through v1.6 Beta), see CHANGELOG.md.

Undrift — Developer Docs

Architecture, module structure, API integration, build process, and technical specifications.

1. Overview

Undrift is a single-file, client-side AI meeting facilitator. No backend server, no runtime build toolchain, no external JS dependencies beyond the Anthropic Messages API.

Tech stack

  • HTML5 / CSS3 / Vanilla JavaScript (ES2020+)
  • Anthropic Messages API — claude-sonnet-4-20250514
  • Web Speech API (browser-native STT, Chrome/Edge only)
  • Web Audio API (browser-native tone generation)
  • Node.js + docx npm package (documentation build only — not shipped)

2. File Structure

FileModuleDescription
undrift-styles.cssStylesDesign tokens, themes, layout, all component CSS including date picker
undrift-setup.jsM1Setup screen: form, date picker, upload, paste-and-parse, SESSION_DATA
undrift-dashboard.jsM2–M6Dashboard: agenda, timers, action buttons, STT, AI engine, feed cards
undrift-report.jsM7Report screen: scorecard, HTML render, markdown download
undrift-build.pyBuildPython script — assembles all parts into undrift-app.html, runs JS obfuscation
api/analyze.jsProxyVercel serverless function — forwards requests to Anthropic, adds API key, enforces rate limiting
proxy.pyDevLocal dev proxy — same role as api/analyze.js for local testing
undrift-docs.htmlDocsCombined user guide + developer docs (this file)
undrift-docs.cssDocsShared styles for documentation and feedback pages
undrift-docs.jsDocsShared JS for documentation pages — theme, version constant
undrift-kit-confirmation.htmlMarketingWaitlist email-verified confirmation page — served at /confirmed
undrift-feedback.htmlDocsFeedback submission form wired to Google Sheets
vercel.jsonConfigVercel routing — hostname routing, path rewrites, /confirmed, /launch, /terms/landing, /privacy/landing

3. Module Breakdown

M1 — Session Setup

undrift-setup.js — Builds SESSION_DATA. Owns form, date picker, file upload, paste-and-parse, and screen transition to dashboard.

M2 — Dashboard Shell

undrift-dashboard.js — Three-panel layout (fixed positioning), topbar, status bar, ResizeObserver, agenda navigation, live clocks.

M3 — Action Buttons

undrift-dashboard.js — Seven buttons wired to capture sheets, feed cards, and SESSION_DATA.flags. Action bar scrolls horizontally.

M4 — STT + Transcript

undrift-dashboard.js — Web Speech API with continuous recognition, auto-restart, language switching, stt-final event pipeline.

M5 — AI Analysis

undrift-dashboard.js — Debounced scope-check. Sends transcript context to Claude, routes structured JSON result to feed.

M6 — Flags + Redirects

undrift-dashboard.js — Feed card rendering, dismiss/park/copy, filter pills, flag counter, Web Audio cues.

M7 — Report

undrift-report.js — Scorecard calculation, report screen render, markdown download. Transitions body[data-mode] to "report".

Key functions — M1

FunctionDescription
initSetup()Bootstraps tabs, date picker (today default), default rows, theme
initDatePicker()Wires display field, hidden date input, calendar icon; sets today as default
parseToISO(raw)Normalizes any date string → YYYY-MM-DD; strips ordinals, falls back to JS native
fmtDateDisplay(iso)Converts YYYY-MM-DD → DD-MMM-YYYY for display
getDateValue()Returns current DD-MMM-YYYY string from display field for SESSION_DATA
launchSession()Validates, builds SESSION_DATA, shows success card
goToDashboard()Sets body[data-mode="dashboard"], calls initDashboard()
parseAgenda()Calls Anthropic API to extract structure from pasted text
applyData(d)Populates all form fields including date picker from parsed data

Key functions — M2

FunctionDescription
initDashboard(sd)Receives SESSION_DATA, populates UI, starts timers
observeStatusbar()ResizeObserver keeps --statusbar-h accurate for fixed layout positioning
renderAgendaList()Builds sidebar with per-item timing rows (pending / active / done states)
jumpToItem(idx)Stamps startedAt/endedAt, advances agenda state
markNextDone()Completes active item, starts next with fresh timestamp

M5 analysis thresholds

SettingDebounceMin wordsCooldown
Gentle (1)8 s40 words20 s
Balanced (2)5 s25 words20 s
Vigilant (3)3 s15 words20 s

M7 score formula

  • 40% — completion rate: items done ÷ total items
  • 40% — on-time rate: items finishing within 110% of allocation
  • 20% — flag penalty: max(0, 100 − totalFlags × 8)

4. SESSION_DATA Contract

window.SESSION_DATA is initialized in M1 and is the single source of truth consumed by all modules.

window.SESSION_DATA = {
  title:           string,
  date:            string,         // DD-MMM-YYYY e.g. "07-Apr-2026"
  duration:        number,         // total minutes
  audience:        string,
  facilitator:     string,
  aggression:      1 | 2 | 3,     // Gentle | Balanced | Vigilant
  aggressionLabel: string,
  agenda: [{
    topic:     string,
    minutes:   number,
    type:      'Discussion' | 'Decision' | 'Info share' | 'Workshop' | 'Q&A' | 'Break',
    status:    'pending' | 'active' | 'done',
    startedAt: number | null,      // Date.now() timestamp
    endedAt:   number | null,
  }],
  outcomes:    string[],
  startedAt:   number,             // session start timestamp
  endedAt:     number | null,
  actionItems: string[],           // populated on endSession()
  parkingLot:  string[],
  notes:       string[],
  flags: [{
    type: 'manual-flag' | 'ai-analysis',
    topic?: string, ts: number,
    onTrack?: boolean, driftSeverity?: string, flagType?: string
  }]
}

5. Anthropic API

Vercel proxy — api/analyze.js

All API calls from the app route through /api/analyze, a Vercel serverless function that adds the API key server-side. The app never has direct access to the key. The proxy forwards the full request body to Anthropic and returns the response verbatim. Rate limiting is enforced at 20 requests per IP per 15-minute window.

Request:  POST /api/analyze  (from browser)
Proxy:    POST https://api.anthropic.com/v1/messages  (with x-api-key from env)
Response: forwarded back to browser

The API key is stored in Vercel environment variables under ANTHROPIC_API_KEY. For local development, it's read from .env.local via vercel dev.

Endpoint

POST https://api.anthropic.com/v1/messages

Common request shape

{
  model:    "claude-sonnet-4-20250514",
  max_tokens: 1000,
  system:   "<dynamic prompt>",
  messages: [{ role: "user", content: "<user content>" }]
}

M1 — Paste & Parse

Called once on "Parse agenda." System prompt instructs the model to return only JSON matching the agenda schema. Regex-based fallback runs if API is unavailable.

M5 — Scope Analysis response schema

{
  onTrack:         boolean,
  driftDetected:   boolean,
  driftSeverity:   "none" | "mild" | "moderate" | "severe",
  driftReason:     string | null,
  suggestRedirect: boolean,
  redirectScript:  string | null,
  flagType:        "none" | "off-topic" | "scope-creep" | "time-risk" | ...,
  flagNote:        string | null,   // max 15 words
  timeRisk:        boolean,
  timeRiskNote:    string | null
}

6. Date Handling

All dates in Undrift are stored and displayed as DD-MMM-YYYY (e.g. 07-Apr-2026). The underlying hidden <input type="date"> stores YYYY-MM-DD and the browser's native picker handles display in the user's locale.

Date picker fields

  • f-date-display — read-only text input showing DD-MMM-YYYY; clicking opens the picker
  • f-date-hidden — hidden type="date" input; stores YYYY-MM-DD; drives the native picker popup
  • f-date-icon — calendar SVG button that calls hidden.showPicker()

parseToISO(raw)

Normalizes any imported date string to YYYY-MM-DD before setting the picker. Pipeline:

  1. If already YYYY-MM-DD — return as-is
  2. Strip ordinal suffixes (7th → 7, 1st → 1)
  3. Pass cleaned string to new Date() — handles most written formats
  4. If still invalid — leave picker at today's default

Imported dates from upload or paste-and-parse always go through parseToISO()fmtDateDisplay() so the displayed date is always consistent with the picker.

7. Theme & Layout

Light/dark toggled via data-theme on <html>. Persisted in localStorage under key undrift_theme.

Dashboard layout — fixed positioning

The dashboard uses position: fixed for all three layers, eliminating height-calculation bugs:

.topbar    { position: fixed; top: 0; height: var(--topbar-h); }
.statusbar { position: fixed; top: var(--topbar-h); }
.layout    { position: fixed; top: calc(var(--topbar-h) + var(--statusbar-h));
             left: 0; right: 0; bottom: 0; }

--statusbar-h is measured live by a ResizeObserver on the statusbar element, so the layout grid always anchors correctly regardless of statusbar reflow.

Scroll behavior by mode

body[data-mode="setup"]     { min-height: 100vh; overflow-y: auto; }
body[data-mode="dashboard"] { height: 100vh; overflow: hidden; }
body[data-mode="report"]    { min-height: 100vh; overflow-y: auto; }

Key CSS custom properties

PropertyLightDarkUsage
--brand#1A6BFF#1A6BFFButtons, active states, links
--bg#F4F6F9#0D1117Page background
--surface#FFFFFF#161B22Cards and panels
--success#16A34A#16A34ACompleted items, on-time
--warning#D97706#D97706Approaching limit, mild drift
--danger#DC2626#DC2626Over time, severe drift
--purple#7C3AED#7C3AEDParking lot, time alerts
--statusbar-hSet by ResizeObserverLayout top offset — never hardcode

8. Build Process

Rebuild app after editing source modules

python undrift-build.py
Note: undrift-app.html is generated — do not edit it directly. Edit the source modules and rebuild.

JS obfuscation

The build script automatically obfuscates the compiled JS block using javascript-obfuscator before injection. Install once with npm install -g javascript-obfuscator. If not installed, the build continues unobfuscated with a warning. All HTML-facing entry point functions are protected via --reserved-names so obfuscation never breaks runtime behavior.

Adding a new module

  1. Create your JS file (e.g. ac-mymodule.js)
  2. Add CSS to undrift-styles.css
  3. In undrift-build.py, read the file and inject into the JS concatenation block
  4. Add any new HTML-facing function names to _ENTRY_POINTS in undrift-build.py
  5. Run python undrift-build.py
  6. Update undrift-docs.html and README.md

9. Changelog

VersionChange
v2.3.0-betaFAQ pagefaq.html added as standalone marketing page; two-level accordion; 7 sections, 29 questions; inline <script> mirrors _safeBlobDownload(), _TPL_CONTENT, downloadTemplateMd(), downloadTemplate() from undrift-setup.js so template links work without loading app JS. Nav updated in index.html and faq.html to include FAQ link.
v2.3.0-betaScorecard CSS fix.sc-card.sc-ok { border-top-color: var(--success) }, .sc-card.sc-warn { border-top-color: var(--warning) }, .sc-card.sc-over { border-top-color: var(--danger) } added to undrift-styles.css; previously missing, causing all cards to render with neutral gray border. Also corrected malformed .sc-card.sc-card.sc-card.sc-label selector — was never matching, preventing .sc-label font/color rules from applying.
v2.2.0-betaMobile responsive layout — Full accordion-based mobile UI triggered at max-width 767px; isMobile() utility gates all behavioral differences; Now Bar (current item + live timer), hamburger menu (animated slide), four accordion sections (Agenda, Objectives, Live Feed, Transcript) with animated open/close via grid-template-rows; live data mirroring for feed cards, transcript lines, agenda, objectives; tap-to-expand transcript capture (Flag, Action, Decision, Parking, Note); mic toggle with getUserMedia() permission flow; report screen 2-col scorecard reflow; setup drop zone hidden after parse on mobile. Files modified: undrift-shell.html, undrift-styles.css, undrift-dashboard.js, undrift-utils.js, undrift-setup.js.
v2.2.0-betaDead code cleanup — Removed 25 orphaned CSS classes, 5 orphaned JS functions (actFlag, actParking, actLogAction, closeTimeCheck, collapseObjIfOpen), stale comment placeholders; all mobile colors converted to CSS variables for automatic dark mode; app size reduced ~6KB (249KB → 243KB).
v2.1.0-betaSocial preview cards — Added Open Graph and Twitter Card meta tags to index.html and undrift-shell.html; enables rich link previews when sharing undrift.io URLs on X, Facebook, LinkedIn; undrift-social-card.png (1200×630px) served from domain root; meta description tags added for improved SEO.
v2.0.1-betaVisual identity refinement and critical fixes — Co-Facilitator gradient identity system implemented across all surfaces (purple #f5f3ff backgrounds, 4px gradient accent stripe, gradient pill labels, white sparkles 18×18px); CF redirect cards redesigned with premium visual hierarchy; mobile warning banner added for viewport < 1024px with localStorage persistence; session end auto-complete bug fixed (agenda item status preserved); sentiment moved to statusbar; facilitator coaching loading indicators enhanced with multi-stage progress; 12px border radius standardized across all cards; QA checklist completion (77 items verified). Files modified: undrift-shell.html, undrift-styles.css, undrift-dashboard.js, undrift-report.js, undrift-docs.html, undrift-docs.css, undrift-terms.html, undrift-privacy.html, vercel.json, all feedback forms.
v2.1.0-betaLegal pages static HTML refactor — Replaced JS rendering system with static HTML; deleted 5 files: undrift-terms-content.js, undrift-privacy-content.js, undrift-legal-render.js, undrift-terms-landing.html, undrift-privacy-landing.html (~1,009 lines total); undrift-terms.html and undrift-privacy.html now fully self-contained; all content inline; no external dependencies except brand.js; faster load, better SEO, simpler maintenance.
v2.1.0-betaDuration validation — Added initDurationValidation() in undrift-setup.js called from showSetupScreen() in undrift-report.js; enforces 60-minute max on duration input; real-time yellow danger toast positioned left: calc(100% + 12px); blocks Preflight Check button via isSetupValid() when value > 60; input[type=number] added to .field styling rule for proper background color.
v2.1.0-betaTimecheck toast repositioning — CSS updated: bottom: 70px → 100px, left: 20px → 300px; yellow styling added via --yellow, --yellow-subtle, --yellow-border variables; timecheck button active class syncs with toast visibility; removed Dismiss button (toggle behavior).
v2.1.0-betaLogo corruption fix — Root cause: undrift_logo.png served as MIME type "document" instead of "image/png"; renamed to undrift-logo.png (hyphen fixes detection); updated references in 9 files: undrift-shell.html, undrift-docs.html, undrift-feedback.html, undrift-privacy.html, undrift-terms.html, undrift-report.js, index.html, undrift-privacy-landing.html, undrift-terms-landing.html; standardized size to 32×32px everywhere.
v2.1.0-betaCSP Google Fonts — Added https://fonts.googleapis.com to style-src and https://fonts.gstatic.com to font-src in vercel.json Content-Security-Policy headers.
v2.1.0-betaDark mode button fixes — Send button: added [data-theme="dark"] .btn-send override to preserve bright blue #1A6BFF; Header buttons: added :visited:hover states to preserve brand blue instead of browser default purple.
v2.1.0-betaLegal metadata — Updated Effective Date from April 10 → April 22, 2026; Version from 1.0 → 2.0.0-beta.0 in both undrift-terms-content.js and undrift-privacy-content.js (before static conversion); rewrote Open Beta Addendum for public open beta (removed confidentiality clauses, invitation requirements, closed beta language).
v2.0.0-betaOpen Beta launch — Public open beta release. Free access, no gate. Channels: Kit.com waitlist + X/LinkedIn. Analytics tracking, NPS feedback, hotfix readiness. Success criteria: 100+ users, 50+ sessions, NPS ≥40, 99%+ uptime.
v1.13.3 BetaSession health enhancements — Enhanced calcScorecard() with weighted Session Health Score (0–100). New signals: drift density, drift recovery rate, objective completion. Facilitator Coaching section in report. Full transcription recording with checkbox include in export.
v1.11 BetaPDF exportdownloadPDF() added; @media print block hides all screen-only chrome; beta ribbon, topbar buttons, co-facilitator pill suppressed in print output.
v1.11 BetaTranscript capture refactorCAPTURE_CONFIGS object centralises icon/title/placeholder/label data; transcriptCapture(bucket, prefill) and transcriptFlag() helpers added; openCapture() gains optional prefill param (backward compatible); transcript line hover highlight via @media CSS; .t-capture-btns pill buttons on hover.
v1.11 BetaSecurity passvercel.json: security headers block added (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy); api/analyze.js: CORS restricted to undrift.io + Vary: Origin; body validation added with safeBody field whitelist; undrift-app.html: maxlength on all text inputs.
v1.11 Betavercel.json routing fix — Host-based rewrites for app.undrift.io replaced with redirect rule; static file serving priority issue resolved.
v1.10 BetaKit integration — Kit.com form replaces Mailchimp; field names: fields[first_name], fields[last_name], email_address; honeypot removed; double opt-in configured; post-verification redirect: https://undrift.io/?confirmed=1.
v1.10 BetaEmail confirmation toast#confirm-toast element added to index.html and undrift-landing-launch.html; IIFE reads URLSearchParams for confirmed=1, adds .show class, removes after 5s, calls history.replaceState to clean URL; CSS uses cubic-bezier(0.34, 1.56, 0.64, 1) spring for entry.
v1.10 Betaundrift-kit-confirmation.html — new page at /confirmed; nav: fixed frosted glass, logo img, same links as index.html; footer: matches index.html exactly (Undrift LLC copy, Terms/Privacy/Contact links, hello@ entity-encoded in Contact); vercel.json route added.
v1.10 BetaLogo standardized to undrift-logo.png — all HTML files and undrift-report.js template literal updated; all paths absolute (https://undrift.io/undrift-logo.png); .brand-icon and .welcome-brand-icon gradient backgrounds and orphaned svg CSS rules removed from undrift-styles.css and undrift-docs.css.
v1.10 Betahello@ obfuscated — entity-encoded in undrift-kit-confirmation.html footer Contact link.
v1.9 BetaSensitivity migrationAGG_LABELS renamed to SENS_LABELS (now declared); slider IDs corrected to f-sensitivity / sens-pill; SESSION_DATA contract carries sensitivity / sensitivityLabel only; parser map populates d.sensitivity; applyData(), initDashboard(), updateSens() all updated; resolves silent crash on session launch.
v1.9 BetaLegal content modulesundrift-terms-content.js and undrift-privacy-content.js are the single source of truth for all legal text; undrift-legal-render.js shared IIFE renderer serves all 4 shell pages; landing-skin shells (undrift-terms-landing.html, undrift-privacy-landing.html) added.
v1.9 BetaLegal shell paths — all script src and link href paths in legal shells changed to absolute / prefix; relative paths caused 404s under Vercel URL rewrites.
v1.9 Betavercel.json — routes added: /launchundrift-landing-launch.html; /terms/landingundrift-terms-landing.html; /privacy/landingundrift-privacy-landing.html.
v1.9 BetaTicker CSS.ticker-strip upgraded to border-image gradient; old flat var(--rim) border definition removed; single definition per page (no fallback version). Applies to both index.html and undrift-landing-launch.html.
v1.9 BetaOrphaned CSS removedundrift-landing-launch.html: .stat-n, .stat-l, .stat-src, .problem-grid, .p-card and all variants removed after problem section redesign.
v1.9 BetaZip manifest rule — files with Drive folder prefix (pm-, qa-, uat-, etc.) excluded from source zip; go in G-Drive-v[version]-[suffix].zip instead.
v1.8 BetaOnboarding gate fixhandleTosChange(), enterApp(), showWelcomeScreen(), showSetupScreen() added; startup router replaces bare initSetup() call with localStorage.getItem('undrift_tos_accepted') check.
v1.8 BetaLaunch button fix — slider HTML IDs corrected: f-sensitivity / sens-pill in HTML now match JS; resolves null ref TypeError in launchSession() and applyData().
v1.8 BetaDomain routingvercel.json updated with has/host hostname conditions: undrift.ioindex.html, app.undrift.ioundrift-app.html, www.undrift.io → 301 to apex. /app fallback route added. undrift-landing.html renamed to index.html.
v1.8 BetaReport download restructure.cf-voice pill extended with .cf-voice-body flex column, .cf-voice-divider, and .cf-voice-footer row housing CTA label + .md/.pdf buttons. Download .md removed from report topbar.
v1.8 BetaLogoundrift-logo.png replaces inline SVGs at all 4 brand icon locations. Favicon added via <link rel="icon">.
v1.5JS obfuscationjavascript-obfuscator added to build pipeline. All JS obfuscated as one unit before injection. Entry points protected via --reserved-names. Graceful fallback if tool not installed.
v1.5Rate limiting — in-memory IP rate limiting added to api/analyze.js (20 req/IP/15 min) and proxy.py (60 req/IP/min). Returns 429 with Retry-After header.
v1.5Combined docs — user guide and developer docs merged into single undrift-docs.html with fixed header, tab bar, scrollable sidebar+content, and fixed footer.
v1.2Vercel proxyapi/analyze.js serverless function routes all API calls server-side. Fetch URLs changed from https://api.anthropic.com/v1/messages to /api/analyze in both undrift-setup.js and undrift-dashboard.js. Fixes CORS errors from file:// origin.
v1.2Feedback formundrift-feedback.html styled feedback page wired to Google Forms/Sheets.
v1.2M5 engine improvements — DEBOUNCE now 6/4/2s, COOLDOWN per-sensitivity 12/8/5s, MIN_WORDS measures new words since last analysis, buildUser() separates RECENT SPEECH from EARLIER CONTEXT.
v1.2parseTemplate fix — now skips <!-- HTML comment --> lines in addition to # comments.
v1.2Deployment infrastructurevercel.json, .env.example, .gitignore, DEPLOYMENT.md, proxy.py added to repo.
v1.1Template format — Agenda template switched from .docx to .md. Accepted upload types: .md, .txt, .csv, .json.
v1.1Date picker — setup form date field replaced with display/hidden/icon picker pattern. All imported dates normalized to YYYY-MM-DD via parseToISO().
v1.1Layout fix — dashboard layout switched to position: fixed anchoring. Eliminates gray dead zone and content clipping.
v1.0Initial release — session setup, live AI scope-check, 7 action buttons, STT, feed cards, end-of-meeting report.

For full version history, see CHANGELOG.md.