Commit graph

28 commits

Author SHA1 Message Date
Jean-Michel Tremblay
e94a17160b Add deploy docs (CICD + Authelia/OIDC); changelog 0.1.0
All checks were successful
Build and Test / build-and-test (push) Successful in 39s
deploy/CICD.md documents which git push/tag triggers which pipeline
steps; deploy/AUTH.md documents the Authelia OIDC integration contract
with a sequence diagram of the login flow. Cross-link from README and
INSTALL. The 0.1.0 release also ships the previously-committed security
hardening (server-side session expiry + nosniff on served files).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:20:08 -04:00
Jean-Michel Tremblay
866bc175fb Security hardening: server-side session expiry + nosniff on files
All checks were successful
Build and Test / build-and-test (push) Successful in 38s
1. Enforce the 12h session lifetime server-side in requireAuth (reject a
   session older than the TTL even if the sealed cookie is intact), so a
   leaked cookie value can't be replayed indefinitely. Shared sessionTTL
   const drives both the cookie MaxAge and the check.
2. Send X-Content-Type-Options: nosniff when serving user-uploaded
   receipt/attachment bytes, so the browser won't sniff past the declared
   (upload-time allowlisted) MIME type.

Update SPEC §2 and §6 accordingly; tests cover stale-session rejection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:56:38 -04:00
Jean-Michel Tremblay
c930f715b7 AI tab: full-width auto-growing note editors
All checks were successful
Build and Test / build-and-test (push) Successful in 37s
Replace the single-line note inputs (which truncated long notes) with
wrapping, auto-growing textareas and a per-note Save/Delete row, so the
whole correction note is readable and editable. Changelog 0.0.4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:17:37 -04:00
Jean-Michel Tremblay
c715c8e0c0 AI classifier correction notes + misread review (AI tab)
All checks were successful
Build and Test / build-and-test (push) Successful in 37s
Add a global, temporal ai_notes list appended to the classifier prompt
(seeded once from no-PII defaults, documented in README), managed inline
on a new AI tab with a read-only view of the assembled prompt. Every
AI-run upload records the browser-round-tripped suggestion blob + model;
misreads are derived (final field != AI guess) and reviewed one by one
(image + per-field guess-vs-entered + notes-since), attributing which
note fixed each or closing unresolved. Update SPEC (new section 10),
DESIGN item 15, README, and changelog (0.0.3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 16:04:30 -04:00
Jean-Michel Tremblay
da7fc56920 Add consolidated SPEC.md; demote spec.md to DESIGN.md journal
All checks were successful
Build and Test / build-and-test (push) Successful in 36s
SPEC.md is the new domain-organized source of truth for current behavior,
reconciled against the code (notes soft-delete/dup-highlight/zip-export as
not-yet-implemented). The old chronological spec.md becomes DESIGN.md, the
why/history journal. Docs only — no behavior change, no release tag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 10:59:46 -04:00
Jean-Michel Tremblay
2ff5c3847c Optional receipt tags; changelog 0.0.2
All checks were successful
Build and Test / build-and-test (push) Successful in 36s
Add a shared, free-form tag vocabulary attachable to a receipt at upload.
New tags/receipt_tags tables (case-insensitive label dedup); an in-page
chip-mosaic picker after "Who" with inline tag creation. Tags are
resolved/created only on successful submit, and shown on the confirm
page and recent lists. Documented as spec item 14.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 08:35:14 -04:00
Jean-Michel Tremblay
7ba0d5abe7 Normalize receipt image orientation from EXIF; add changelog (0.0.1)
All checks were successful
Build and Test / build-and-test (push) Successful in 37s
Bake the EXIF Orientation rotation into uploaded JPEG pixels (and strip
the tag) so receipts are upright in every consumer, not just EXIF-aware
viewers. Acts only when orientation is known (tag 2..8); images with no
tag, tag 1, non-JPEG, or PDFs pass through byte-for-byte. Wired into the
receipt, attachments, and AI-classify paths. Documented as spec item 13.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:26:03 -04:00
Jean-Michel Tremblay
ef2b0665b8 Merge branch 'ci-auto-deploy-release-tags'
All checks were successful
Build and Test / build-and-test (push) Successful in 37s
2026-06-19 20:03:46 -04:00
Jean-Michel Tremblay
810f44dab8 CI: auto-deploy on release tags, drop manual deploy workflow
All checks were successful
Build and Test / build-and-test (push) Successful in 35s
Replace the workflow_dispatch deploy with an Activate step in build.yml
that runs only for X.Y.Z tags: it repoints the ~/hsa-app/hsa symlink and
restarts the user service. Pre-release tags (e.g. 0.0.0a2) are still
built, tested, and staged into releases/ but not activated. Remove
deploy.yml and update INSTALL.md accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:45:50 -04:00
Jean-Michel Tremblay
3bb0e5f678 Merge branch 'ci-forgejo-build'
All checks were successful
Build and Test / build-and-test (push) Successful in 33s
2026-06-19 15:13:14 -04:00
Jean-Michel Tremblay
fafd763d99 Split deploy: auto-stage on tag, manual activate via button
All checks were successful
Build and Test / build-and-test (push) Successful in 34s
build.yml now only stages the binary into ~/hsa-app/releases on tag
(no symlink swap or restart). Activation is a separate workflow_dispatch
workflow (deploy.yml) the user triggers from the Forgejo Actions tab,
passing the tag to point the symlink at and restart. Update INSTALL.md
to describe the stage/activate split.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:12:22 -04:00
Jean-Michel Tremblay
f47e611e2c Deploy tagged releases via versioned dir + symlink swap
All checks were successful
Build and Test / build-and-test (push) Successful in 36s
On tag push, scp the binary to ~/hsa-app/releases/hsa-app-V<tag>/hsa,
repoint the ~/hsa-app/hsa symlink, and restart the user systemd
service. Add deploy/hsa_app.service unit and deploy/INSTALL.md with
host layout, one-time install, deploy, and rollback steps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:08:20 -04:00
Jean-Michel Tremblay
2f817d16d0 CI: deploy binary to versioned dir on tag push
All checks were successful
Build and Test / build-and-test (push) Successful in 35s
On a tag push, scp the built hsa binary to ~/hsa-app-V<tag> on the
target host (created if missing). Reuses the FORGEJO_SSH key; host and
user come from the HSA_APP_HOST / HSA_APP_USER repo vars. Branch
pushes still only build and test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:44:16 -04:00
Jean-Michel Tremblay
cddc42b32d CI: disable VCS stamping in containerized build
All checks were successful
Build and Test / build-and-test (push) Successful in 34s
git inside the golang container flags the mounted repo as dubious
ownership (different UID), so `go build` fails obtaining VCS status
(exit 128). The binary doesn't need git version stamping here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:39:14 -04:00
Jean-Michel Tremblay
5bf03feb94 CI: target the shell runner label instead of docker
Some checks failed
Build and Test / build-and-test (push) Failing after 36s
The only registered act_runner advertises the `shell` label, not
`docker`, so `runs-on: docker` left the job queued forever. The host
has the docker CLI (the resume repo builds via `docker run` on this
same runner), so keep running the build/test inside a golang container.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:36:25 -04:00
Jean-Michel Tremblay
15335a9f4c Add Forgejo Actions workflow to build and test on push
Some checks are pending
Build and Test / build-and-test (push) Waiting to run
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:31:20 -04:00
Jean-Michel Tremblay
42fd0a2af4 Recent list: show upload date only, drop the time
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 09:12:16 -04:00
Jean-Michel Tremblay
011f033f4b Recent list: full first-name initials for hyphenated names
Jean-Michel -> JM (every hyphen-separated part), Lynna -> L. Adds a shortWho test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 09:04:11 -04:00
Jean-Michel Tremblay
b92787be7a Recent list: initial the first name, keep timestamp from wrapping
Abbreviate "First Last" → "F. Last" in the recent list and nowrap the upload
timestamp, so rows fit on narrow phone screens instead of wrapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 09:01:09 -04:00
Jean-Michel Tremblay
2a3077fa26 Compact recent list to one line per receipt
Drop the (usually garbage) original filename. Each row is now a single line:
amount link · date · category · who, attachments as 📎 links (filename on hover),
and a small muted upload time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 08:54:37 -04:00
Jean-Michel Tremblay
99b8541da0 Static portable build + env-file launcher for deployment
- build.sh: CGO_ENABLED=0 + -ldflags "-s -w" → fully static binary (pure-Go
  SQLite, no C toolchain), runs on any linux/<arch>; note GOARCH cross-compile.
- scripts/hsa-app.sh: load an env file (default /etc/hsa-app/hsa-app.env, or arg/
  $HSA_ENV_FILE), export it, then exec the binary ($HSA_BIN or ./hsa next to it).
  Suitable as a systemd ExecStart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 08:32:19 -04:00
Jean-Michel Tremblay
a7566ed166 Verify DB integrity at boot; document crash recovery (spec item 12)
- Open() now runs PRAGMA quick_check after WAL recovery. WAL mode already makes
  opening self-healing (committed writes rolled forward, an interrupted write
  discarded), so a crash mid-write recovers automatically; quick_check fails fast
  only on genuine corruption, pointing the operator at BACKUP_DIR.
- spec item 12 documents the durability/recovery model and the orphan-file caveat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 07:43:15 -04:00
Jean-Michel Tremblay
885fae06ec Backups: ROOT/dbbackup, hsa_sqlite_backup_YYYY_MM_DD.db naming
- Backup dir default ./data/dbbackup; files named hsa_sqlite_backup_<YYYY_MM_DD>.db
  (one per day; a same-day re-run is a no-op since the dated file exists).
- Simplify the scheduler: attempt on start, then tick every BACKUP_INTERVAL.
  Per-day idempotency makes restarts and sub-day intervals settle at one/day —
  removes the date-parsing untilNext/newestBackup logic (and its busy-loop edge).
- BACKUP_INTERVAL remains the frequency knob (Go duration; 0 disables).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 07:34:31 -04:00
Jean-Michel Tremblay
fbfdc6877d Add scheduled metadata-only DB backups (spec item 11)
- internal/backup: weekly (configurable) snapshots via the existing VACUUM INTO
  + blob-strip path; restart-safe (only backs up if newest is older than the
  interval), retains BACKUP_KEEP most recent, prunes the rest. Runs in a
  background goroutine; failures are logged, never fatal.
- Config: BACKUP_DIR (./data/backups), BACKUP_INTERVAL (168h; 0 disables),
  BACKUP_KEEP (8).
- Fix: blob-strip now clears attachments.image_data too, not just receipts.
- Fix: STORAGE_DIR is the storage ROOT (default ./data) — receipts/ and
  attachments/ live under it; corrects the doubled-nesting from item 10.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 07:12:50 -04:00
Jean-Michel Tremblay
04d6e8177d Add receipt attachments captured at upload time (spec item 10)
- New attachments table (FK to receipts), dual-write: file on disk + DB blob.
- On-disk layout split: receipts under <ROOT>/receipts/<YYYY>/..., attachments
  under <ROOT>/attachments/<YYYY>/..., attachments named from the parent
  receipt's date+amount stem (_att, _att_1, ...).
- Upload form gains an optional multi-file "Additional files" field; the files
  ride along with POST /upload, saved after the receipt row exists. No AI runs
  on attachments; primary-image auto-fill unchanged.
- GET /attachment/{id}/file serves blobs; confirm page lists them; recent list
  links them. Adding attachments to an already-saved receipt is not yet supported.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 07:06:15 -04:00
Jean-Michel Tremblay
e723e7989d Store receipt files under year/MM_DD_dollars.cents naming
Replace flat UUID filenames with a browsable, dated layout (spec.md item 9):
<STORAGE_DIR>/<YYYY>/<MM>_<DD>_<dollars>.<cents><ext>, from the receipt date
and amount. Same date+amount collisions get a _1, _2, … suffix via exclusive
create (race-safe). New uploads only; serving is unaffected (blob-backed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:10:41 -04:00
Jean-Michel Tremblay
5c21f131fd Add AI auto-fill, tally, recent views, and cheaper-by-default classification
Features (see spec.md v2):
- Wire receipt classification into the upload flow; cheap model (Haiku 4.5)
  is now the default, shown as a footnote with per-scan cost in cents.
- Skip-AI toggle to enter fields by hand.
- Duplicate-transaction warning: live check on date+amount, gated submit.
- Tally tab: person x year totals with margins and grand total.
- Recent uploads / recent receipts tabs with paging and file serving.
- People reconcile on startup: merge stray partial names (e.g. "Jude" ->
  "Jude Tremblay"), reassigning receipts; idempotent seeding.
- scripts/build.sh builds the binary; scripts/run.sh builds and runs with .env.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:04:42 -04:00
Jean-Michel Tremblay
8b7252dad4 Initial commit: HSA receipt tracker
Go app for capturing and archiving HSA-eligible receipts: OIDC/PKCE auth
against Authelia, SQLite storage with dual-write (filesystem + DB blob),
mobile-first upload, and DB export.

Adds AI receipt classification: a config.json catalog of people and
categories (seeded into the DB on startup), a prompt builder that derives
name-order/initial variants from the data (with same-surname ambiguity
handling), and an Anthropic tool-use client behind POST /classify. Tests
run against a mock endpoint; a live integration test is env-gated to the
cheapest model.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 21:40:12 -04:00