The PackageInstaller session's final status now reaches UpdateRepository:
InstallResultReceiver hands it by session id to the waiting AndroidApkInstaller
call (ApkInstaller.install returns a PendingInstall). installOutcome() maps
STATUS_FAILURE_BLOCKED to UpdateFailure.Blocked, STATUS_FAILURE_ABORTED back to
Available without an error, and every other failure to UpdateFailure.Install
with the installer's EXTRA_STATUS_MESSAGE. The cached APK is deleted once the
installer answers, so "Try again" downloads again. UpdateBanner's Failed variant
shows the reason on S-4 and S-22; two core:design strings (en/sv) with their
docs/ux/strings.csv rows. Unit tests for the mapping, the repository outcomes
and the receiver; goldens: update_banner re-recorded, S-17-install-blocked,
S-17-install-failed and s22_about_install_blocked new.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
InsightsRepository in :core:model (SpendTrends, SpendForecast and parts); :data DTOs for E-34/E-35
(round-trip the contract examples, fixing ExampleCoverageTest), InsightsRepositoryImpl through KvittApi and
OnlineGate with category/account names from Room (deleted rows included); FakeInsightsRepository with
scenarios (loaded, empty household, account without history, offline, unreachable, long); the Insights
section below S-11's charts: household trend and per-category trends on SpendLineChart with average and
moving average, a forecast card per account, "Needs connection" / "Could not reach the server" over the last
data of the session. Strings sv/en; 41 goldens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
web/ is the browser client's scaffold: Vite 8, React 19, TypeScript 5.9 strict, React Router 7 (basename /app, every
fixed route with a lazy placeholder page), TanStack Query 5 (refetch on focus and every 60 s), openapi-typescript
types generated from contracts/openapi.yaml with openapi-fetch. Reads go through GET /sync (no REST reads exist for
the ledger, SPEC §7): useLedger() pulls from cursor 0 and then incrementally, restarting on a new epoch or
cursor_ahead. Login pairs the browser as "Web · <browser>" with an invite code (E-3); every call sends the SPEC
§14.3.1 headers, a 401 signs out. kvitt-domain's split, balances, allocation and settlement rules are ported to
web/src/lib and pass every case of contracts/test-vectors. Design tokens from design-spec §2 in tokens.css
(light-dark()), Material Symbols Rounded (Apache-2.0) as generated inline SVG, sv/en catalogs generated from
docs/ux/strings.csv plus web-strings.csv. Gate: cd web && npm run check.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
D-135 /app/ serving from KVITT_WEB_DIR (fallback, caching, CSP, path safety); D-136 /app on the
unauthenticated per-IP limit; D-137 repository-root image build with the Node 24 stage and the allowlist
.dockerignore; D-138 E-34 trends and E-35 forecast with the rules in new §14.5.7; D-139
publish-apk.sh --deploy. infra/README.md build context; HANDOFF state and numbering (next D-141, v0.31).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
UpdateRepository.checkNow() (default: check(), for throttle-free fakes) skips
the 6-hour throttle of the automatic check (AC-16.2) and restarts its window;
AboutViewModel calls it. Debug builds still never check (AC-16.5).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
Part A (D-132): KVITT_WEB_DIR (default /app) served under /app/ by a small handler: /app redirects to
/app/, app routes fall back to index.html, a missing assets/* file is 404, assets cached immutable,
index.html no-store, the app's own CSP plus nosniff, DENY and no-referrer. Unsafe paths never reach the
file system. /app paths count against the unauthenticated per-IP limit. Without the directory every /app
path stays 404 not_found. server/Dockerfile gains a Node 24 LTS stage (pinned by digest) building web/,
passed through the Rust stage so npm and rustc never run at once on the Pi 3; the build context is the
repository root with an allowlist .dockerignore (server/.dockerignore removed); workflow, build-images.sh
and infra/dev/build.sh follow.
Part B (D-133): GET /analytics/trends (E-34) and GET /analytics/forecast (E-35), authenticated, read-only,
SQL month buckets; rules (half-up average, trailing 3-month moving average, percent against the rounded
average, template occurrence per month) in kvitt-domain::analytics. OpenAPI, examples, contracts-check
and tests updated (35 operations).
Cargo.lock: rustls-webpki 0.103.16 and webpki-root-certs 1.0.10 (RUSTSEC-2026-0382, -0383) so that
cargo audit passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
New SPEC §15 Web client and FR-21; §1, §2, G-5, AC-14.1 revised; §8.5 and
§10.5.2 gate lines for /app/, the invite accept and bearer requests (except
/health, /setup/, /i/, /apk/) plus the /app/ CSP via $kvitt_csp, identical in
infra/npm/kvitt.conf and infra/README.md step 4 (checked with nginx 1.31.7);
§10.3, §10.6, §11.1 to §11.3 (rows W0 to W7), §14.7 ownership; change log is
now §16. PLAN-W0..W7 and prompts/W0..W7; HANDOFF numbering D-139 / v0.30.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
A dedicated forced-command key (scp -t into data/apk only) copies
kvitt-<code>.apk then latest.json with legacy scp (-O), host key pinned in
infra/secrets/pi_known_hosts on first use, then curls
https://$KVITT_PUBLIC_HOST/apk/latest with X-Kvitt-App (fed on stdin) and
requires the new version_code. Without --deploy nothing changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
New AC-14.11; §13.3 S-12 row with its own range and granularity, the line
chart and the empty-range text; §13.4 strings.csv 569 rows. design-spec
§3.10 SpendLineChart and LineLegend, AnalyticsFilterBar row 2 optional,
S-12 layout for a category or account over the range. strings.csv gains
the seven analytics keys as built in 12e2508. HANDOFF: D-128 and the
keyboard fix landed, web-client programme holds D-129+ and v0.28+.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
A drill-down opened from a breakdown row now shows its own period chip and
Month/Week toggle (initialised from S-11, S-11 keeps its own filter), the
header total, a line chart of the group's spend per bucket with the dashed
range average, the trailing 3-bucket moving average and a legend, and a
caption comparing the latest full bucket with the average; the expense rows
follow. Bucket drill-downs are unchanged. The series is the group's column of
AnalyticsState.buckets from spendSeries, so no contract change.
:core:design chart: SpendLineChart and LineLegend beside StackedBarChart,
shared axis/slot helpers (bar chart pixels unchanged), AnalyticsFilterBar
measure and group-by toggles optional.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
AC-15.3, §9.9 and §13.3 (S-4, S-5) name the pull as a sync now, not
disabled offline. design-spec: S-4's "Pull-to-refresh is not offered"
replaced; S-4/S-5 refreshing frames, interactions and TalkBack;
KvittPullToRefresh in §3.6; ContainedLoadingIndicator row in §3.1;
spatial fast and the named motion in §2.11. HANDOFF: D-127 landed
(3778f5f), Pi live, both phones paired, import done; next free D-129 /
v0.28 after the parallel D-128 (v0.27).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
The recurring, category and account editors drew their scrolling body
inside KvittScaffold with only the scaffold padding, so with adjustResize
and edge-to-edge the keyboard overlaid the content and a focused field
(S-19 day of month, notes) stayed hidden. Each body now applies
padding(padding).consumeWindowInsets(padding).imePadding(), as S-7 does
with imePadding(): the viewport shrinks above the keyboard and Compose's
scroll container keeps the focused text field in view. Consuming the
scaffold padding first stops the navigation inset (or the S-19 keypad
bottom bar) from being counted twice.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
KvittPullToRefresh in :core:design wraps Material 3's PullToRefreshBox with
Kvitt's own indicator: a primary-container disc (the hero's first-sync tile
look) with the sync icon, which winds with the pull while a primary arc fills
to the threshold, pops at the threshold with a GestureThresholdActivate haptic,
and turns into an indeterminate ring while the sync runs. Material 3 1.4.0 has
no LoadingIndicator or LinearWavyProgressIndicator, so the circular indicator
stands in; the swap happens in Refresh.kt only.
Home (S-4) and account detail (S-5) call syncNow() of SyncRepository, the path
of S-16's "Synka nu"; the indicator stays until it returns, success or failure.
Offline the pull ends at once without a sync. TalkBack reads "Synkar…"
(sync_syncing) while refreshing; no new string keys.
New goldens S-4-home-refreshing and S-5-account-refreshing; ViewModel tests
with GatedSyncRepository (core:design test fixtures).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
Outside the home LAN NPM closes every request without X-Kvitt-App: <key>
(return 444); the LAN check reads $realip_remote_addr (NPM's X-Real-IP
realip makes $remote_addr forgeable) and Let's Encrypt's challenge passes
on the normalised $uri. Block rehearsed in NPM v2.16.0's openresty.
README: step 4a (key, keyed APK, gate, acceptance test), health checks,
stale CARGO_BUILD_JOBS text and owner decisions fixed. HANDOFF as of
2026-10-21.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
The reverse proxy closes requests from outside the home LAN that lack
X-Kvitt-App: <key>. The key is the build property kvitt.appKey
(-Pkvitt.appKey, else android/local.properties, else empty = no header),
validated as 16-128 printable ASCII without whitespace, exposed as
BuildConfig.APP_KEY and passed via AppBuildInfo.appKey to KvittApi
(every call, /health and other unauthenticated calls included) and the
Coil picture client (PictureAuth). AppBuildInfo.toString hides the key.
Tests use a made-up key; README documents the property.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
D-125 (owner decision 2026-10-20, landed 6e5981d): the Kvitt Pi runs Kvitt
from /home/jacobhagstedt/kvitt, a self-contained compose project with
relative bind mounts, both containers as the owner's uid/gid, the seven
.env keys compose reads, no scripts and nothing under /opt; operations are
plain docker compose commands. Revises D-103, D-105, D-117 point 5 and
D-120 point 6. SPEC §10.2, §10.6, §10.8 to §10.10, §11.2 and §14.7
rewritten; KVITT_PI_HOST placeholders become <kvitt-pi-address>.
§10.3 records the Gitea workflow's first results (run 69: v1.0.0, backup
5 min 20 s, server 2 h 42 min; v1.0.1 from the sparse-checkout push) and
the sparse checkout of 3820f9c. HANDOFF, prompts/README.md and notes in
PLAN-M11.md and PLAN-M2c-pi.md follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
The Kvitt Pi runs Kvitt from /home/jacobhagstedt/kvitt: compose.yaml, .env,
secrets/ and relative bind mounts ./data, ./backups, ./import. Both containers
run as the owner's uid/gid (KVITT_UID, KVITT_GID). No install script, no /opt,
no Docker daemon or system changes; operations are plain docker compose
commands (README "Pi: what the owner does", RESTORE.md).
Removed install.sh, deploy.sh, bootstrap.sh, backup-now.sh, install-apk.sh,
lib.sh. .env holds only the keys compose.yaml reads; RCLONE_REMOTE is fixed
to gdrive: in compose.yaml.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
KVITT_PI_HOST and KVITT_PI_SSH_USER were read by nothing on the Pi (D-103).
They are gone from .env.example; the README's scp, ssh and NPM steps use
<user>@<kvitt-pi-address> and the Kvitt Pi's LAN IPv4 address in words.
install.sh checks only the keys the stack reads, so an older .env that still
has the two keys installs unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
Checkout is a cone-mode sparse checkout of server and infra/backup (the
two build contexts), which makes actions/checkout fetch with
--filter=blob:none. fetch-depth stays 0: with depth 1, fetch-tags only
auto-follows tags on the fetched commit, and the version step needs
every v1.0.* tag. The temporary secrets diagnostic step is gone and the
header names kvitt_cicd as the owner's user-level secret.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
D-124 (owner decision 2026-10-19; a86ad24, 4852803, 038c603): Kvitt is served
at https://<host> with no explicit port; the router's existing external 443
forward to the Home Assistant Pi serves it, no 8443 rule. Every :8443 in SPEC
replaced; D-120 to D-123 struck through with pointers. The pinned-root config
is generated from its template with kvitt.publicHost (038c603). HANDOFF,
prompts/README.md status and the S-1 TalkBack line follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
D-124 serves Kvitt at https://<host> on 443. The README, the AppBuildInfo and
AboutRepositoryImpl comments, the S-21/S-22 fake scenarios and the :data test
fixtures drop the :8443 port. S-21's invite-created goldens and every S-22
golden re-recorded (link and server row now portless). The release-URL
normaliser test checks a portless URL and keeps one explicit-port case (a
typed port survives), on example.org instead of the owner's old host.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
- Pinned-root network security config (D-082) is now a template in
app/src/releasePinnedRoot/; with kvitt.tls.pinnedRoot=true the task
generatePinnedRootNetworkSecurityConfig writes it into the release
resources with the domain kvitt.publicHost (D-121), and stops with a
clear message while the root src/release/res/raw/kvitt_root_ca.crt is
not committed (fallback unprepared, SPEC 10.5.3). Comment: pinned NPM
root (D-120).
- Port 443 (D-124): RELEASE_SERVER_URL is https://<kvitt.publicHost>
without a port (RELEASE_SERVER_PORT removed); the AC-2.6 intent filter
has no android:port; InviteLink drops an explicit default port.
- S-1 fixture GOLDEN_SERVER https://kvitt.example.org; 28 S-1 goldens
re-recorded (only the server row changed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
KVITT_PUBLIC_URL=https://${KVITT_PUBLIC_HOST} with no port; the health check
through NPM prints the port-less URL; publish-apk.sh's summary drops :8443.
README: no new router rule (the existing external 443 forward to the Home
Assistant Pi serves Kvitt, NPM selects by name), NPM listens on 443, the
fallback, operations and APK-abuse notes, and every :8443 URL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
Kvitt is served on the default HTTPS port through Nginx Proxy Manager, so the
OpenAPI server is https://{host} (contracts/openapi.yaml and openapi.rs
PUBLIC_SERVER_URL), examples E-7 public_url and E-12 link are
https://kvitt.example.invalid without a port, tools/contracts-check.py expects
the portless header, and the server's tests, config message and README name
no port.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
D-122 (4a4e45f, 4b1ae0c): AppBuildInfo.releaseServerUrl, S-22 before pairing, publish-apk.sh reads
KVITT_PUBLIC_HOST from infra/pi/.env. D-123 (c9abd24, e381e37): KVITT_PUBLIC_URL required,
KVITT_TRUSTED_PROXY defaults to none, OpenAPI server https://{host}:8443, E-7/E-12 examples on
kvitt.example.invalid. The owner's host name kvitt.suorra.duckdns.org recorded in HANDOFF (SPEC §10.1,
§10.5.1 point there). HANDOFF and prompts/README.md status at ac17b41.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
KVITT_PUBLIC_URL is required: every command refuses to start without it (compose
always sets it). KVITT_TRUSTED_PROXY defaults to none, which the rate limiter never
resolves. contracts/openapi.yaml and the utoipa document: servers[].url
https://{host}:8443 with variable host (default kvitt.example.invalid). E-7 and E-12
examples on kvitt.example.invalid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
Reads KVITT_PUBLIC_HOST from infra/pi/.env without sourcing it, refuses a missing file or key, a CHANGE_ME value,
anything that is not a host name, and icehack.asuscomm.com (the checks of infra/pi/install.sh), passes it to
:app:assembleRelease as -Pkvitt.publicHost and prints the release server in the header and summary. README
publish paragraph updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
AppBuildInfo gains releaseServerUrl, which :app fills from RELEASE_SERVER_URL (https://<kvitt.publicHost>:8443,
D-121). AboutRepositoryImpl shows it while no server is paired, in place of DataModule.DEFAULT_SERVER_URL, which
is deleted: :data no longer names a host. The fakes' AboutState uses the fixture https://kvitt.example.org:8443;
30 S-21/S-22 goldens re-recorded (only the host changed; oxipng -o 4 --strip safe). New test: unpaired About shows
the release server.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
Prints the lengths of secrets.kvitt_cicd, secrets.KVITT_CICD, the control
secrets.babymonitor_cicd, secrets.GITEA_TOKEN and github.token, plus the
repository, owner, actor, event, ref and server URL, before the registry
login. Values are never printed. Remove once the empty-secret cause is known.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
SPEC §10.5 rewritten (10.5.1 Nginx Proxy Manager, 10.5.2 Custom Nginx Configuration, 10.5.3 Fallback:
NPM down); Caddy and cert-sync text replaced in §1, §6.1, §7, §8.5, §8.7, §8.9, §10 intro, §10.1 to §10.4,
§10.7, §10.10, §11.2 to §11.4, OQ-3, OQ-6, E-7 and §14.7; the Kvitt host name is KVITT_PUBLIC_HOST.
D-121: kvitt.publicHost, RELEASE_SERVER_URL and @ReleaseServerUrl (AC-2.6, AC-2.7, §10.6, §13.3 S-1).
Decisions D-120 and D-121 with pointers in D-023, D-103 to D-106 and D-117; HANDOFF; prompts/README;
a superseded-in-part note on PLAN-M2c-pi; dated D-120 notes under M-2, L-1, L-2, L-8 and I-5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
The release server's host name comes from -Pkvitt.publicHost, else kvitt.publicHost in
android/local.properties, else the placeholder kvitt.example.invalid. :app exposes it as
BuildConfig.PUBLIC_HOST, the manifest placeholder publicHost (the https invite-link intent
filter, port 8443) and RELEASE_SERVER_URL, which :feature:onboarding receives through the
@ReleaseServerUrl Hilt qualifier for S-1's prefill. Tests and goldens use fixed hosts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
Owner checklist without the SSH add-on step: KVITT_PUBLIC_HOST placeholder, NPM's Details, SSL and
Advanced tab fields, the Custom Nginx Configuration block (security headers of D-117 at server level, its
own location / so NPM's X-Served-By does not drop them, /i/ kept out of NPM's access and error logs), what
NPM logs, the HSTS caveat, the router rule external 8443 to the Home Assistant Pi's 443, the NPM-down
fallback (no LAN path for release builds), operations and troubleshooting. RESTORE checks port 8080.
Rehearsal of 2026-10-19 recorded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
The server publishes 8080 on the Kvitt Pi's LAN interfaces and trusts X-Forwarded-For only from HA_HOST,
NPM's LAN address (an IPv4 literal, checked by install.sh). KVITT_PUBLIC_HOST (owner-chosen, not
icehack.asuscomm.com, which is NPM's proxy host for Home Assistant) builds KVITT_PUBLIC_URL. Only
fcm-service-account.json and rclone.conf are installed; install.sh removes the Caddyfile, the Home Assistant
SSH key, known_hosts and the certs and caddy_data volumes left by the earlier stack. Health checks: port 8080,
then through NPM. The cert-sync image leaves the workflow and build-images.sh.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
The server gate becomes cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo audit && cargo test
(CLAUDE.md, server/README.md). The pre-commit hook runs cargo audit --no-fetch only when server/Cargo.lock or a
server Cargo.toml is staged, and skips with a message when cargo-audit or the advisory database is missing.
First run: 0 advisories, 0 warnings over 319 crates; no audit.toml needed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
D-119 (landed 96e4cbd, security review L-3): E-6 at most 10 requests per
minute per client IP in its own window, not counted against the 60 of the
other unauthenticated endpoints; SPEC §8.6, §10.6, E-6. M-1 closed by the
owner as an accepted risk (§8.1, §8.7); the D-114 limits confirmed (§11.4).
HANDOFF: main and origin at 96e4cbd, NPM rework (D-120) in flight, owner
steps and questions rewritten, numbering next D-121 and v0.21.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
E-6 (/apk/files/{file_name}) gets its own per-IP bucket of 10 requests per
minute (security review L-3), keyed like the 60/min unauthenticated limit
(peer or trusted-proxy X-Forwarded-For, D-032) and answered 429
rate_limited with Retry-After. E-6 no longer counts against the 60; E-5 and
E-4 stay on the 60/min limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
AndroidApplicationPlugin and AndroidLibraryPlugin repeated the SDK levels, Java 17, unit-test resources, lint, Kotlin,
Robolectric JVM and JUnit set-up; both call configureAndroidModule(CommonExtension) now (the gate's task inputs did
not change: every test task stayed up to date). :app imports nothing from coil-compose; it still arrives through
:core:design, so the runtime classpaths are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
* Milestone and ownership notes that no longer hold ("owned by M5/M6/M8", "M8 replaces it", "(M8b)", "M7x add",
"proposed decision in the M5 report", which is D-027 and D-028) in build scripts, :core:design, :core:fakes, :data,
:app, :core:model and the settings string files.
* SyncEngine: the clock_skew KDoc sat orphaned above restampAfterClockSkew's; it now documents renewMutationId.
* libs.versions.toml header: no milestone, the drift date.
* README: the module table as built (no owners, no DebugFakesModule, which M8 removed), the test fixtures in the
architecture check and the shared screenshot harness.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
DESCRIPTION_MAX (S-7 and S-19 each had 100), NOTES_MAX (S-7's 1000 and NotesField's private copy), FIRST_DATE and
DAYS_AHEAD_MAX (the S-7 and S-9 date pickers each spelled 2000-01-01 and 365). Values unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
Nothing referenced any of these:
* :core:design KvittDimens iconMeta, seesawWidth, KEYPAD_COLUMNS, presetMinHeight, navIndicatorWidth/Height;
KvittShapes largeIncreased, extraLargeIncreased, extraExtraLarge (the FAB's 20 is `fab`); KvittSpacing
navigationBarHeight (ShortNavigationBar draws its own 64 dp); KvittMotion's spatial/effects springs, hero count,
bunting stagger, shared-axis, sheet-enter and keypad-swap values (only the seesaw spring is used); the
segmentedItem builder and snackbarMillis (KvittSnackbarVisuals maps the duration itself); KvittIcon.Description
with its two drawables.
* Catalogue sample Samples.renamedGroceries; fake scenarios SettleScenarios.partnerOwes and longAccountIds.
* :data Clock.todayDate().
* Unused imports in Common.kt, Settle.kt, Timeline.kt, AnalyticsScenarios.kt, Clock.kt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
* format/Failures.kt: FAILURE_NETWORK and commonFailureText, the §14.3.2 code-to-text map that :feature:account
(Errors.kt) and :feature:settings (Messages.kt) each spelled out; both keep only their own codes. The "network"
literals in :feature:onboarding and the two NETWORK constants use FAILURE_NETWORK.
* component/Split.kt: savedSplitLabel, the S-8 and S-16 line ("Kontots standard 50/50", "Egen fördelning 70/30",
"Exakta belopp", "Allt på dig/Anna") that ExpenseDetailScreen.modeLabel and SyncStatusScreen.lostModeLabel
duplicated line for line.
* format/Limits.kt: BP_TOTAL (five copies in four features and a literal in :core:design and S-19) and
DISPLAY_NAME_MAX (three copies in :feature:onboarding and :feature:settings).
Behaviour and goldens unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B