Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ jobs:
- name: Token enforcement
run: npm run check-tokens

- name: Strings-module enforcement
run: npm run check-strings

- name: Contrast audit (WCAG AA over token pairs, both themes)
run: npm run check-contrast

Expand Down
47 changes: 47 additions & 0 deletions .github/workflows/release-prep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,55 @@ concurrency:
cancel-in-progress: false

jobs:
# Quality gate over main before anything is bumped, committed, tagged, or
# pushed. rustfmt is the only Rust check here — no compilation, so the
# ubuntu runner needs no llama-server prebuild.
gate:
name: Quality gate
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
with:
ref: main

- uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Test
run: npm run test

- name: Type-check + Vite build
run: npm run build

- name: Token enforcement
run: npm run check-tokens

- name: Strings-module enforcement
run: npm run check-strings

- name: Contrast (WCAG AA) enforcement
run: npm run check-contrast

- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt

- name: cargo fmt
working-directory: src-tauri
run: cargo fmt --check

prep:
name: Bump, tag, trigger build
needs: gate
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ jobs:
tagName: ${{ github.ref_name }}
releaseName: 'StudyVis ${{ github.ref_name }}'
releaseBody: |
Friends-only unsigned release. See INSTALL.md for first-run instructions:
Friends-only release — the macOS app is ad-hoc signed (not notarized);
the Windows installer is unsigned. See INSTALL.md for first-run steps:
macOS users right-click the app and choose Open the first time;
Windows users click "More info" → "Run anyway" on the SmartScreen warning.

Expand Down
82 changes: 70 additions & 12 deletions ARCHITECTURE.md

Large diffs are not rendered by default.

111 changes: 111 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,117 @@ V3 work was drafted as v1.0.4 but shipped under the **v1.0.5** tag —
there is no v1.0.4 tag; the section below is labelled by the tag that
shipped it.)

## Unreleased — reliability, honesty, and quality-of-life pass

A broad maintenance + feature wave across eight clusters, drawn from the
`IMPROVEMENTS.md` backlog. Reliability of friend-finding and live
sessions, more honest AI and stats, safer identity error paths, new
notifications and custom pomodoro durations, a stricter accessibility
gate, and release/CI hardening. New outbound behaviour stays opt-in and
OFF by default; the one sanctioned outbound request is carved out in
PLAN §3.

### Added

- **Connection diagnostics + your own relays/TURN (Settings → Network).**
A live per-relay status panel (state by glyph + text, never color
alone) and fields to add your own signaling relay URLs and a TURN
server without a new build — the one path through strict/CGNAT
networks now that no public TURN ships.
- **Friends-list backup.** Export / Import friends to a sealed
`.svfriends` file encrypted to your own key; import upserts. The
recovery gap (24 words restore only the keypair) is now self-serve.
- **Focus insights (Stats).** A local, cross-session view of when
distractions cluster, recurring reasons, and a focused-time trend —
read from `audit_events` on-device, nothing transmitted.
- **File exports.** Save the post-session report (markdown), a raw
per-session audit JSON, and a stats CSV of daily study minutes +
partner counts.
- **Session history management.** Delete a single session
(Settings → Sessions) or clear all history (Advanced), behind confirm
dialogs; stats and the report follow.
- **Pomodoro break/work OS notifications** (opt-out, ON by default) and a
gentle phase-transition chime (opt-in, OFF by default) — so a break
boundary is visible while the window is in the tray.
- **"Friend came online" notification** (opt-in, OFF by default), honest
about the ~60 s presence latency.
- **Custom pomodoro durations** (5–120 work / 1–60 rest) with a
backward-compatible wire: explicit durations ride alongside a
legacy-preset fallback, so a custom-split host never strands a friend
on an older build.
- **Camera on/off toggle, audio-output picker, and a per-peer volume
slider** in the session footer.
- **"Waiting for your friend" tile** when you're alone in a session, and
per-peer connection states (connecting / failed) instead of a frozen
offline tile.
- **Opt-in new-version check (Settings → About), OFF by default.** When
on, a single unauthenticated GET to the public GitHub Releases API
compares tags and shows a quiet update row; zero outbound while off,
silent on failure. This is the one sanctioned outbound request beyond
P2P + Nostr signaling — carved out in PLAN §3.
- **`studyvis://` deep link.** A pairing link now prefills (never
auto-connects) the add-a-friend form; relaunching a tray-hidden app
focuses the existing window (single-instance guard).
- **Quit confirmation during an active session.**

### Changed

- **Honest AI focus pipeline.** Malformed/empty model responses, and
low-confidence off-task calls below the `off_task_confidence_floor`
(default 0.6, with a Settings → AI slider), are now treated as
_uncertain_ skips — they neither reset an off-task streak nor count
toward focused-time %, instead of being fabricated as `on_task`. The
benchmark and live request are built from one shared builder so the
predicted cadence is achievable. A duration-based cadence backoff
replaces the dangling "thermal-aware notice" (engages after 2 slow
ticks vs the benchmark p95, recovers after 3 normal ticks). Model
downloads resume from a surviving `.tmp` via HTTP Range.
- **Honest scores and labels.** AI-off sessions no longer persist a
fabricated `score=100` — the report shows a calm no-score state and
averages skip nulls. Stats' "Focused minutes" is renamed "Study
minutes" so "Focused" stays the AI concept; the average-score tile
says how many sessions it covers.
- **Legible connection failures.** The pairing dialog distinguishes
"can't reach the network" (your side) from "your friend hasn't
arrived"; an invite to an offline friend retries when they flip online
(deduped) and reads differently from a relay-down failure; a
best-effort goodbye flips presence offline near-instantly on quit.
- **Accessibility gate proves coverage.** `check-contrast` now scans
`src/` for every text/bg/border token co-occurrence and fails on any
pairing missing from the allowlist — not just that the listed pairs
pass. Surfaced previously-unlisted real pairings, all AA-verified.
- **Always-visible invite button**, onboarding **Back** navigation, one
CTA on the zero-friends empty state, and the SessionTimer presets now
use the themed `RadioGroup` primitive.

### Fixed

- **Push-to-talk can no longer latch the mic open** — a dropped release
event or a stale latch can never bring a fresh session's first audio
track up live (a privacy defect); a stuck-key guard and per-session
reset back it up.
- **Grace window before auto-ending.** A transient transport drop no
longer ends a long session instantly — a 20 s grace window cancels on
any rejoin.
- **Corrupt-identity and corrupt-DB safety.** An unreadable
`identity.json` routes to a calm Retry/Restore screen and can never be
steered into new-identity onboarding that clobbers keychain keys; a
corrupt `app.db` is set aside and recreated with an explanatory dialog
instead of a startup panic; a DB written by a newer build is refused
distinctly. Recovery now skips the overwrite warning when you re-type
the same 24 words and preserves your display name.

### Release / CI

- **CI-green gate before release.** `release-prep` runs lint, test,
build, check-tokens, check-strings, and `cargo fmt --check` before any
version bump, tag, or push lands on `main`; `check-strings` also runs
in `ci.yml`.
- **macOS ad-hoc signing** (signing identity `-`, hardened runtime off)
softens first-run Gatekeeper friction to the milder "unverified
developer" prompt. The dormant `tauri-plugin-updater` dependency was
removed (re-add checklist in PLAN §8).

## 1.2.0 — 2026-06-07 — post-1.0 fixes and feature improvements

A maintenance and feature pass on top of the 1.0 line: audit-verified
Expand Down
9 changes: 7 additions & 2 deletions DESIGN-SYSTEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ These components only import from `ui/`, `design/tokens.ts`, and shared utilitie
|-|-|
| `VideoTile` | One peer's video + name + per-tile status dot + PTT indicator. |
| `VideoGrid` | Mesh layout of tiles (1, 2, 3, or 4). Aspect-aware. |
| `WaitingTile` | Calm "waiting for your friend" tile shown beside the self tile when alone in an active session. §10 empty-state pattern, no spinner. `invite` / `reconnect` variants. |
| `AudioOutputPicker` | Speaker/headphone output selector for the session footer (`setSinkId`). Feature-detected — renders nothing where unsupported (macOS WKWebView). |
| `FocusIndicator` | Per-tile dot: `focused` / `warning` / `alerted` / `offline`. |
| `PttIndicator` | Visible while a peer is transmitting audio. |
| `AuditLogPanel` | Right-rail panel listing `AuditEvent`s. |
Expand All @@ -301,8 +303,11 @@ These components only import from `ui/`, `design/tokens.ts`, and shared utilitie
| `FriendsList` | Scrollable list of friends, online dots, last-studied label. |
| `FriendRow` | Single friend with `Invite` button. |
| `AddFriendDialog` | 12-word generate / paste flow. |
| `RelayDiagnostics` | Settings → Network: live per-relay connection status (one row per signaling WebSocket, polled while mounted). Status by glyph + text, never color alone. |
| `FocusInsights` | Cross-session focus insights for the Stats dashboard: distraction timing buckets, recurring reasons, focused-time trend. Pure presentational over computed `statsInsights` data. |
| `IdentityLoadErrorView` | Calm "we couldn't read your identity file" screen (Retry + Restore). Deliberately offers no create-new path so a still-valid keychain identity is never abandoned. |
| `OnboardingStep` | Full-bleed onboarding surface, single CTA, optional secondary. |
| `BipBackupPanel` | Mono-font 24-word display + copy + "I've saved them" confirmation. *Currently inlined in `src/features/identity/IdentitySetup.tsx`; pending extraction to a standalone component (V3 polish).* |
| `BipBackupPanel` | Mono-font 24-word display + copy + "I've saved them" confirmation. Standalone component (`src/components/BipBackupPanel.tsx`), imported by `IdentitySetup.tsx`, with its own Storybook story. |
| `SessionTimer` | Pomodoro timer with phase indicator, broadcaster badge if you're broadcasting. |
| `ModelPicker` | (V2) Radio cards: name, size, RAM, measured speed badge. |
| `BenchmarkRunner` | (V2) 30-second benchmark progress display. |
Expand Down Expand Up @@ -410,7 +415,7 @@ Mono font for the wordlist. Accent only on the active "Continue" button (disable
└──────────────────────────────────────────────────────────────────────┘
```

Online dot uses `status.online`; offline uses `status.offline`. Invite button is `accent` variant; appears only on hover for online friends to keep the list calm.
Online dot uses `status.online`; offline uses `status.offline`. The Invite button on online friend rows is always visible at reduced emphasis (`outline` variant at rest) so the action is discoverable on first look and reachable on touch, and elevates to the `accent` fill on row hover / keyboard focus to keep the list calm.

### 8.3 Session view (3 peers, AI off — V1)

Expand Down
10 changes: 6 additions & 4 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ StudyVis ships unsigned installers for a friends-only audience. Each OS will war

> StudyVis does **not** auto-update. When a new version is available, download the latest installer from the [GitHub Releases page](https://github.com/scotej/studyvis/releases) and re-run the install steps for your OS.

## macOS (Apple Silicon + Intel)
## macOS (Apple Silicon)

1. From the [Releases page](https://github.com/scotej/studyvis/releases), download the `.dmg` for your Mac's chip: `StudyVis_<version>_aarch64.dmg` for Apple Silicon (M1/M2/M3/M4), or `StudyVis_<version>_x64.dmg` for Intel. (In → About This Mac, "Apple M…" = Apple Silicon, "Intel" = Intel.)
> StudyVis ships an Apple Silicon (`aarch64`) `.dmg` only. Intel Macs are not in the release matrix. (In → About This Mac, "Apple M…" = Apple Silicon.)

1. From the [Releases page](https://github.com/scotej/studyvis/releases), download `StudyVis_<version>_aarch64.dmg`.
2. Double-click the `.dmg`. A window opens showing the StudyVis icon and an Applications shortcut. Drag StudyVis into Applications.
3. Open Finder → Applications. **Right-click** (or Control-click) the StudyVis icon and choose **Open**. macOS shows: _"macOS cannot verify the developer of 'StudyVis'. Are you sure you want to open it?"_. Click **Open**.
3. Open Finder → Applications. **Right-click** (or Control-click) the StudyVis icon and choose **Open**. The app is ad-hoc signed, so macOS shows the milder _"macOS cannot verify the developer of 'StudyVis'. Are you sure you want to open it?"_ prompt — not a hard block. Click **Open**.
4. Subsequent launches do not re-prompt — double-click works normally.
5. The first time you join a session, macOS asks for camera and microphone permission. Allow both. (Screen-recording permission is requested separately, and only if you turn on AI features.)

Expand All @@ -34,6 +36,6 @@ Your identity, friends list, and local session history live in your OS data dire

## Troubleshooting

- **macOS, "App is damaged and can't be opened"** — this happens when the `.dmg` is downloaded with quarantine flagged but right-click → Open is skipped. From Terminal: `xattr -dr com.apple.quarantine /Applications/StudyVis.app`, then double-click again.
- **macOS, "App is damaged and can't be opened"** — uncommon now that the app is ad-hoc signed (the usual first-run prompt is the milder "cannot verify the developer" one above), but it can still happen on a stubborn download where quarantine is flagged and right-click → Open is skipped. From Terminal: `xattr -dr com.apple.quarantine /Applications/StudyVis.app`, then double-click again.
- **Windows, SmartScreen does not show "More info"** — make sure you're running Windows 10 1903 or later. Older builds present a different dialog.
- **Camera or mic permission denied at first launch** — open the OS privacy panel (macOS System Settings → Privacy & Security; Windows Settings → Privacy & security → Camera/Microphone) and grant StudyVis access manually, then relaunch.
Loading