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
31 changes: 31 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Code owners are requested for review automatically on every pull request
# that touches a matching path. This file documents the reality that `main`
# is admin-protected: @AdamXweb is the only account that can merge.
#
# Syntax: https://docs.github.com/articles/about-code-owners
# Later matches win, so the catch-all goes first.

* @AdamXweb

# Surfaces where a mistake is expensive or hard to reverse. Called out
# individually so the review request is obviously deliberate rather than
# an artefact of the catch-all.

# Release + CI plumbing: a bad workflow can publish or sign something.
/.github/workflows/ @AdamXweb
/Dockerfile @AdamXweb
/docker-compose.yml @AdamXweb

# Desktop shell: Tauri capabilities and IPC commands are a security
# boundary (see AGENTS.md on the three-place command registration).
/src-tauri/ @AdamXweb

# Storage layer: schema migrations run eagerly on boot against real user
# data, and the permissions contract lives here.
/lib/db.ts @AdamXweb
/lib/migrations/ @AdamXweb

# Disclosure surfaces — legal and privacy copy shown to users.
/app/legal/ @AdamXweb
/app/privacy-policy/ @AdamXweb
/.github/SECURITY.md @AdamXweb
40 changes: 40 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!--
Thanks for the contribution. Keep this short — the diff says what
changed, so use the description to say why.
Full guidance: CONTRIBUTING.md
-->

## What and why

<!-- One or two sentences. What problem does this solve? If you found
something surprising along the way, mention it — that context is
usually the most valuable part of a PR. -->

## How it was verified

<!-- What you actually ran, and what it said. Delete lines that don't
apply; add anything else you exercised (a specific flow, a device,
a locale). "Should work" isn't verification. -->

- [ ] `pnpm lint`
- [ ] `pnpm typecheck`
- [ ] `pnpm test`
- [ ] `pnpm lint:i18n` (if any copy or locale keys changed)
- [ ] `pnpm test:e2e` (if any user-facing UI changed)
- [ ] Exercised the change in the running app

## Checklist

- [ ] One coherent change — not several unrelated fixes bundled together
- [ ] New user-facing strings go through `locales/en.json` (and are
mirrored into the other locales)
- [ ] New interactive UI is reachable and operable by keyboard
- [ ] The a11y gate's known-issue allowlist in `tests/e2e/a11y.spec.ts`
is still empty — or a new entry names the pending fix that will
delete it
- [ ] No hand-bumped dependency versions (Renovate owns those)

## Screenshots

<!-- Before/after for any visual change. Include a phone-width shot if
you touched layout — several past regressions were mobile-only. -->
96 changes: 96 additions & 0 deletions .github/workflows/repo-settings-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#
# Repository settings audit — scheduled drift check.
#
# Runs the read-only `scripts/audit-github-settings.mjs` on a weekly cron so
# a silently-flipped setting (branch protection weakened, secret scanning
# switched off, a required check renamed) surfaces on its own rather than
# the next time someone happens to run the script by hand. The script only
# READS the GitHub API — it never changes a setting.
#
# Token (Settings ▸ Secrets and variables ▸ Actions ▸ REPO_AUDIT_TOKEN):
# OPTIONAL, but without it the audit is partial. The built-in GITHUB_TOKEN
# cannot read `repos/{repo}/branches/main/protection` (that endpoint needs
# repo-admin), so the branch-protection half of the report degrades to
# "[check] main branch protection readable/enabled" on every run — a
# permanent false alarm. Add a fine-grained PAT with `administration:read`
# + `metadata:read` on this repository to get the full picture. Same class
# of problem, and same fix, as RENOVATE_TOKEN in renovate.yml.
#
# Failure policy: ADVISORY by default. A scheduled job that goes red every
# week for a setting nobody intends to change is a job people learn to
# ignore, so the cron run always exits 0 and writes its findings to the job
# summary. Run it manually with `strict: true` when you want a hard pass/fail
# (e.g. before a release, or to confirm a fix).
#
name: Repo settings audit

on:
schedule:
# Mondays 07:00 UTC — before the week's PRs land, after Renovate's
# Sunday-night run so a settings change it triggered is visible.
- cron: '0 7 * * 1'
workflow_dispatch:
inputs:
strict:
description: 'Fail the job when a setting needs attention'
type: boolean
default: false

permissions:
contents: read

jobs:
audit:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Setup Node
# No pnpm install needed — the audit script is stdlib-only and shells
# out to the `gh` CLI, which is preinstalled on GitHub runners.
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: '24'

- name: Run audit
id: audit
env:
# Falls back to GITHUB_TOKEN so the workflow still produces a
# useful (if partial) report before anyone configures the PAT.
GH_TOKEN: ${{ secrets.REPO_AUDIT_TOKEN || secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
set -o pipefail
if [ -z "${{ secrets.REPO_AUDIT_TOKEN }}" ]; then
echo "::notice::REPO_AUDIT_TOKEN is not set — running with GITHUB_TOKEN. Branch-protection checks will report as unreadable; see this workflow's header comment."
fi
STRICT=""
if [ "${{ inputs.strict }}" = "true" ]; then
STRICT="--strict"
fi
node scripts/audit-github-settings.mjs $STRICT | tee audit-output.txt
continue-on-error: true

- name: Publish report to job summary
if: always()
run: |
{
echo "## Repository settings audit"
echo ""
echo '```'
cat audit-output.txt 2>/dev/null || echo "(no output — the audit step failed before producing a report)"
echo '```'
echo ""
echo "Lines marked \`[check]\` need attention. This report is advisory;"
echo "re-run from the Actions tab with **strict** enabled for a hard pass/fail."
} >> "$GITHUB_STEP_SUMMARY"

- name: Enforce result
# Only the manual strict run can fail the job — see the failure
# policy in this file's header.
if: inputs.strict == true && steps.audit.outcome == 'failure'
run: |
echo "::error::Repository settings audit failed in strict mode — see the job summary."
exit 1
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,8 @@ storybook-static
# Local a11y/e2e iteration builds (PLAYWRIGHT_PORT=3001 + NEXT_DIST_DIR=.next-e2e,
# so the suite can run alongside a long-lived `next dev` holding :3000 and .next)
/.next-e2e

# Captured UI screenshots (`pnpm screenshots`). The capture script is
# checked in so anyone can produce a consistent set; the images themselves
# are for docs, issues, and release notes rather than repo weight.
/docs/screenshots/
45 changes: 41 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ pnpm lint:i18n # check locales/*.json key parity against en.json
```

The repo enforces pnpm via `"packageManager": "pnpm@11.1.2"` in
`package.json` and ships a `pnpm-lock.yaml` / `pnpm-workspace.yaml`. All
six GitHub workflows run `pnpm install --frozen-lockfile`. Using `npm`
locally will mostly work against the pnpm lockfile but is unsupported
and risks drift.
`package.json` and ships a `pnpm-lock.yaml` / `pnpm-workspace.yaml`.
Every workflow that installs dependencies (six of the ten) runs
`pnpm install --frozen-lockfile`. Using `npm` locally will mostly work
against the pnpm lockfile but is unsupported and risks drift.

Docker (production): `docker compose up --build -d`. By default the SQLite DB lives in a Docker-managed **named volume** (`privacytracker-data`), so data survives rebuilds and the container's non-root `audit` user (uid 100 / gid 101) can write it on any host with no setup. Back it up with `docker compose cp web:/app/data ./data-backup`. This is a change from the old `./data` bind mount, which broke on a fresh Linux host: Docker auto-creates the bind source as `root:root`, uid 100 can't create `privacy.db`, and `lib/db.ts` throws `SQLITE_CANTOPEN` (macOS Docker Desktop hid this by uid-mapping bind mounts). If you'd rather keep the DB on the host at `./data/privacy.db`, layer on `docker-compose.bind-mount.yml` after the one-time `mkdir -p data && sudo chown 100:101 data`: `docker compose -f docker-compose.yml -f docker-compose.bind-mount.yml up --build -d`. The `compose-smoke` CI job exercises both paths (and asserts `/fonts/InterVariable.woff2` + `/brand-icon.png` actually serve — the runtime image must copy `public/`).

Expand All @@ -34,6 +34,43 @@ Dependency bumps are driven by **Renovate**, not Dependabot — `.github/dependa

Activation is one of two mutually-exclusive paths (pick one): the self-hosted `.github/workflows/renovate.yml` (weekly cron + a `workflow_dispatch` **dry-run** button that previews the PR without opening it — needs a `RENOVATE_TOKEN` secret for live-run PRs to trigger CI, since GITHUB_TOKEN-authored PRs don't), **or** the hosted Mend Renovate GitHub App (its PRs trigger CI automatically; delete the workflow if you install the app). Both read the same `renovate.json`. See the header comment in the workflow for the token rationale.

## Repo settings drift check

`scripts/audit-github-settings.mjs` (`pnpm audit:repo-settings`) is a
**read-only** check that the repo's GitHub settings still match intent —
branch protection on `main`, required checks (`quality`, `container-smoke`),
secret scanning, Dependabot alerts, CodeQL. `.github/workflows/repo-settings-audit.yml`
runs it weekly and writes the report to the job summary.

Two things to know before trusting a green run: the cron leg is **advisory**
(it always exits 0 — use the `workflow_dispatch` run with `strict: true` for
a hard pass/fail), and without a `REPO_AUDIT_TOKEN` secret the branch-protection
half is *unreadable* rather than *passing*, because `GITHUB_TOKEN` lacks
repo-admin scope for that endpoint. A fine-grained PAT with
`administration:read` fixes it; same rationale as `RENOVATE_TOKEN`.

`pnpm screenshots` (`scripts/capture-screenshots.mjs`) captures a
consistent set of UI screenshots into `docs/screenshots/` — the apps
grid, an app detail page, the dashboard's risk sections, the privacy map,
and a phone-width shot. Output is **gitignored**: the images are for
docs, issues, and release notes, not the repo. It needs a production
build served on :3001 with a **disposable** data dir, because it calls
`/api/reset` and seeds the canned demo fixture (never real data). The
script disables the coachmark tour before capturing; without that the
dashboard shot is a dimmed spotlight overlay. Two capture quirks are
commented in the script: `scrollIntoViewIfNeeded()` no-ops when the
target is already partly visible (hence the dashboard's explicit scroll
offset past the first-run checklist), and the phone shot is taken at the
top of the page because mid-scroll the sort pills bleed through the
translucent nav.

Contributor-facing docs (`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`,
`SUPPORT.md`, `CHANGELOG.md`, `.github/PULL_REQUEST_TEMPLATE.md`,
`.github/CODEOWNERS`) are the human counterpart to this file. Keep
CONTRIBUTING's command table in sync with the Commands section above, and
add a `CHANGELOG.md` entry under `## [Unreleased]` for anything a user
would notice.

## Architecture

This is a Next.js 16 App Router app (TypeScript, React 19) backed by a single local SQLite file. All scraping, parsing, diffing, and AI calls happen server-side inside API routes that import helpers from `lib/`. End-to-end workflow diagrams (system map, import/delete/sync/wayback flows, gate chain) with known weak points marked live in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
Expand Down
114 changes: 114 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Changelog

All notable changes to this project are documented here.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Entries for `v0.1.0` – `v0.1.2` were written retrospectively from the
[GitHub release notes](https://github.com/privacykey/privacytracker/releases);
they summarise each release rather than listing every merged pull request.
Going forward, changes are recorded here as they land.

## [Unreleased]

### Added

- Blocking accessibility gate in CI: axe-core scans of the welcome screen,
onboarding import flow, dashboard, app detail, and mobile navigation, plus
keyboard-only coverage of the onboarding path.
- Community health documentation — contributing guide, code of conduct,
support guide, pull-request template, and code owners.
- `pnpm screenshots` — captures a consistent set of UI screenshots from
the built-in demo fixture, for docs and release notes.

### Changed

- **First-run experience.** Per-feature toggles moved behind an "Advanced"
disclosure, illustrated goal cards shrunk on phones, and the primary action
pinned to a sticky footer so it stays reachable. AI summaries now default to
**Disabled** instead of preselecting a provider, and a stored "disabled"
choice is honoured on reload. "Save & generate" stays disabled until the
provider's fields validate. New users now get exactly one post-onboarding
guide — the task checklist — instead of a checklist plus a coachmark tour
pointing at it.
- Import candidate selection is now a native radio group: keyboard-operable
with arrow keys, and announced correctly by screen readers.

### Fixed

- **The SQLite database is now private by default** — `0700` on the data
directory, `0600` on the database and its write-ahead-log files. Existing
installations are tightened automatically on their next start. The file
holds your full app inventory, your notes, and (for now) any configured AI
provider key.
- Accessible names restored for the icon-only home and "Add Apps" links in the
compact navigation bar.
- Expandable section headers no longer nest their info-tooltip button inside
the toggle, and the collapsed notes sidebar no longer keeps invisible
controls in the tab order.
- The app-name entry field has a real label rather than only a placeholder.
- Colour contrast now meets WCAG AA across the interface: link and secondary
text colours, the accent blue in light mode, and the navigation drawer were
all below the 4.5:1 threshold in places.

### Security

- Documented in the README that a configured AI provider key is stored in
plaintext in the local database. Moving desktop keys into the OS keychain is
planned.

## [0.1.2] — 2026-06-12

### Added

- Animated onboarding purpose cards and dashboard vignettes.
- Periodic health check with non-destructive self-heal for long-running
instances.
- Read-only deployment mode for shared or kiosk installs.

### Changed

- Privacy-label icons and ordering aligned with Apple's own presentation.
- Full internationalisation sweep — the interface is translator-ready and
round-trips through Crowdin.
- Onboarding hardening across the four import paths.

## [0.1.1] — 2026-05-20

### Fixed

- **Launch-time freeze affecting every copy of v0.1.0.** The bundled Node
helper exited immediately with `MODULE_NOT_FOUND` for `@swc/helpers`,
leaving an unresponsive window. The packaging step had dereferenced pnpm's
symlinked `node_modules` layout, moving `@swc/helpers` out of Node's
resolution path; it now preserves those relative symlinks verbatim through
both staging and the release tarball.

The auto-updater runs *after* the Node helper boots, so it never fired on
v0.1.0 — anyone on that version had to install v0.1.1 manually. Every
install from v0.1.1 onward self-updates normally.

## [0.1.0] — 2026-05-18

Initial beta release, available as a macOS app, a Docker image, or a plain
Next.js app.

### Added

- App Store privacy-label tracking with change detection over time.
- Historical back-fill to Q1 2021 via the Wayback Machine.
- Focus-tailored dashboard adapting to who the device belongs to (yourself, a
loved one, or someone you support) and what you want from it.
- Four onboarding import paths: typed names, CSV/TXT upload, Apple
Configurator on desktop, and screenshot OCR.
- Changelog timelines, privacy heatmap, per-app severity strips, an editable
home-card layout, and exportable audit bundles.
- AI-generated privacy-policy summaries with a bring-your-own provider model.
- Background sync with a notifications bell, and crash-safe resume across the
live, Wayback, and privacy-policy jobs.

[Unreleased]: https://github.com/privacykey/privacytracker/compare/v0.1.2...HEAD
[0.1.2]: https://github.com/privacykey/privacytracker/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/privacykey/privacytracker/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/privacykey/privacytracker/releases/tag/v0.1.0
Loading
Loading