persistent "stay signed in" session cookies across every bchen.dev app (both #18

Closed
opened 2026-05-13 19:02:16 +00:00 by brendan · 0 comments
Owner

Originally filed: 2026-05-08 in ~/features.md, block #16.
Cross-project companion issues: brendan/authd, brendan/buchinese, brendan/dashcam, brendan/inventory, brendan/movement

2026-05-08 — cross-project: persistent "stay signed in" session cookies across every bchen.dev app (both authd-SSO and own-login flows)

User wants: once you sign in to any bchen.dev app, you stay signed in until you explicitly clear your cookies (or hit a logout button). No more "open the laptop next morning and get bounced to /login because the session cookie was a session-only cookie that died with the browser."

Source request (verbatim from user): "for every app (including those using the new authd signin + old individual signin), make it so you stay signed in unless you clear your cookies"

In scope — all 6 apps with auth/session surfaces:

  • authd (~/authd) — DONE (PR #7 merged 2026-05-09 at 562aca5). 30-day sliding token cookie + JWT, single mint primitive issueSessionCookie, mfa-pending guard + logout-path guard preserved. JWT_EXPIRY env var dropped.
  • inventory (~/inventory) — NOT yet an authd client; migration filed as the separate item above (2026-05-08, "migrate inventory authentication to authd … both human login AND MCP"). The migration also rips out inventory's MCP-OAuth-provider surface entirely (per user direction, 2026-05-08). After that lands, inventory has exactly one cookie path — inventory_session set by the authd handshake — and one Bearer-token path for MCP traffic, both keyed by authd sub. Persistence work for inventory then becomes "authd SSO cookie + inventory_session cookie + a long enough sessions-table TTL for MCP refresh-token rotations to keep up". Sequence accordingly: do the migration first, then sweep inventory along with the other authd-SSO clients (inventory, buchinese, dashcam) for the long-Max-Age treatment.
  • buchinese (~/buchinese) — DONE (PR #3 merged 2026-05-09 at 9015328). 30-day persistent buchinese_session cookie + sliding renewal at most once per hour. Family constants reused verbatim. Two-layer logout-resurrection defense (architectural + URL guard).
  • nanodrop (~/nanodrop) — DONE (PR #4 merged 2026-05-09 at aed9931). 30-day persistent nanodrop_session cookie + sliding renewal at most once per hour. Family constants reused verbatim. Stateless-JWT architecture (mirrors authd; nanodrop has no sessions table). Both logout paths URL-guarded. JWT_EXPIRY env var removed.
  • dashcam (~/dashcam) — DONE (PR #2 merged 2026-05-09 at 4459450). 30-day persistent dashcam_session cookie + sliding renewal at most once per hour. Family constants reused verbatim. Stateful sessions-DB architecture (mirrors buchinese). sessions table gained expires_at column with idempotent ALTER + last_seen_at backfill. Two-layer logout-resurrection defense (architectural + URL guard with query-string strip). Tampered-ciphertext hardening (try/catch wrap returns null instead of 500). Single-mint primitive consolidates the cookie write at exactly one site.
  • movement (~/movement) — DONE (PR #13 merged 2026-05-10 at be39c07). 30-day persistent movement_session cookie + sliding renewal at most once per hour. Family constants reused verbatim. Stateless-JWT architecture (mirrors authd/nanodrop; movement has no sessions table). Hard-cut rename from tokenmovement_session. Both logout paths URL-guarded. JWT_EXPIRY env var removed. sameSite='strict' preserved (stricter than family default 'lax', kept per never-relax invariant).

Out of scope: portman, tradebot — no auth surface (verified by survey on 2026-05-08).

What "stay signed in" means in practice (the spec):

  1. Replace session-only cookies with persistent cookies. Today the suspicion is that some/all of these apps set their session cookie without a Max-Age / Expires attribute, which makes the browser drop it when the window closes. Set an explicit long expiry. Recommended default across the family: 30 days, sliding (rolling) — every authenticated request bumps the cookie's expiry forward another 30 days. So an active user effectively never gets logged out; an inactive user's session lapses 30 days after their last visit. The implementer may pick 90 days if there's a reason, but pick one number and use it consistently across all 6 apps so the family feels coherent.
  2. For authd-SSO clients (inventory, buchinese, …): both layers need persistence — the authd SSO cookie at auth.bchen.dev (so re-auth on the SSO server isn't required when an app's local session lapses), AND each downstream app's own session cookie (so the app doesn't bounce through OAuth on every visit). If authd issues refresh tokens, ensure the access-token-refresh path is wired up so a still-valid refresh token avoids bouncing the user to /login.
  3. For own-login apps (nanodrop, dashcam, movement, …): their single session cookie just needs the long Max-Age + sliding-renewal middleware.
  4. Security attributes preserved or strengthened, not relaxed. Every cookie touched must keep Secure, HttpOnly, SameSite=Lax (or Strict where the app already uses it). Cookie value still rotates on login (no fixation). Idle-rolling expiry uses the existing session token; do not extend a token after sign-out.
  5. Logout still ends the session. Explicit logout button → cookie cleared server-side AND a Set-Cookie: <name>=; Max-Age=0 sent to the browser. "Stay signed in unless you clear your cookies" = until cookies expire OR the user actively clears them OR the user clicks logout.
  6. Server-side session record matches. Whatever sliding expiry the cookie uses, the server-side session record (DB row, redis entry, JWT exp claim, etc.) must be aligned — otherwise the cookie says "valid for 30 more days" while the server already considers it expired and bounces the user.

Plan / approach:

  1. Survey pass per app — for each of the 6 repos above, locate where the session cookie is set (search for cookie, Set-Cookie, maxAge, max_age, expires, session). Note current cookie attributes and current server-side session lifetime.
  2. Pick the family-wide constant — 30 days sliding by default. File-level constant (e.g. SESSION_TTL_DAYS = 30) so it's tunable per repo without hunting through the codebase.
  3. Implement per repo — one PR per app. Each PR: (a) cookie attrs updated, (b) sliding-renewal middleware added if missing, (c) server-side session lifetime aligned, (d) logout path verified to still clear cookies + server record, (e) tests covering the new persistence + logout-still-works behavior.
  4. Order: authd first (SSO cookie propagates), then the OAuth clients (inventory, buchinese), then the own-login apps (nanodrop, dashcam, movement). Each PR independent — no cross-repo merge dependency, but landing authd first lets you smoke-test the OAuth-client changes against a long-lived SSO cookie.
  5. Standard implementer pipeline per PR: plan → execute → review → merge.

Acceptance:

  • For each of the 6 apps: signing in, closing the browser, reopening it the next day → still signed in. (The implementer will need a smoke-test plan covering this — at minimum, set a cookie, kill the browser process, restart, hit the app, expect to land on the authed page not /login.)
  • For each of the 6 apps: clicking the logout button → bounced to login (and the cookie is gone in devtools).
  • For each of the 6 apps: clearing site cookies in devtools → bounced to login on next request.
  • For each of the 6 apps: a session that has not been touched for >TTL_DAYS → bounced to login on next request (the inactive-user lapse case).
  • No regression in security headers / cookie flags. Secure, HttpOnly, SameSite attributes preserved.
  • Build + tests green per repo; PRs land on main via the implementer pipeline.

Notes for the implementer:

  • This is one umbrella ask but it splits cleanly into 6 independent PRs — do not bundle into one mega-PR. File follow-ups back into ~/features.md if any per-app implementation hits a snag that warrants its own item.
  • If any of the 6 apps already has 30-day-sliding cookies, that PR is a no-op + a confirmation note in the PR description; don't churn the file just to "match" — leave it alone.
  • The recent inventory PR #5 and authd PR #6 work shows the family is converging on shared conventions; this is in the same spirit. Keep the chosen SESSION_TTL_DAYS constant identical across all 6 repos so future audits are trivial.

Source: user via reporter chat (2026-05-08).

> **Originally filed:** 2026-05-08 in ~/features.md, block #16. > **Cross-project companion issues:** brendan/authd, brendan/buchinese, brendan/dashcam, brendan/inventory, brendan/movement **2026-05-08 — cross-project: persistent "stay signed in" session cookies across every bchen.dev app (both authd-SSO and own-login flows)** User wants: once you sign in to any bchen.dev app, you stay signed in until you explicitly clear your cookies (or hit a logout button). No more "open the laptop next morning and get bounced to /login because the session cookie was a session-only cookie that died with the browser." **Source request (verbatim from user):** "for every app (including those using the new authd signin + old individual signin), make it so you stay signed in unless you clear your cookies" <!-- PROGRESS 2026-05-09 — authd portion DONE. PR https://gitea.bchen.dev/brendan/authd/pulls/7 merged at 562aca5d037a8d790aa8264916c8bce0b84e7b9b. Family-wide constants set: SESSION_TTL_DAYS=30, SESSION_TTL_SECONDS=2_592_000, SESSION_RENEW_THRESHOLD_SECONDS=3600. Subsequent PRs (inventory, buchinese, nanodrop, dashcam, movement) MUST reuse these exact constants for coherence. --> <!-- PROGRESS 2026-05-09T13:32Z — buchinese portion DONE. PR https://gitea.bchen.dev/brendan/buchinese/pulls/3 merged at 9015328b22d57ee16350d9d0583c09b8bf0f24cd. Reviewed SHA c3ecb98d454fb2cfa88a1df936a3b2609dfbb489 (feat 5ff2cda + refactor c3ecb98). 129/129 vitest specs green (was 115; +14 new). Coverage on changed files: stmts 91.93% / lines 91.45% / branches 86.84% / funcs 95%. Constants reused verbatim from authd PR #7 (SESSION_TTL_DAYS=30, SESSION_TTL_SECONDS=2_592_000, SESSION_RENEW_THRESHOLD_SECONDS=3600); new buchinese-specific SESSION_COOKIE='buchinese_session'. Single-mint primitive issueSessionCookie in src/middleware/auth.ts; sliding renewal in src/middleware/session-renewal.ts wired into requireSession+requireApiSession (NOT loadSession, so /logout never resurrects); two-layer logout-resurrection defense (architectural placement + LOGOUT_PATH URL guard with query-string strip). sessions table gained expires_at column with idempotent ALTER + last_seen_at-based backfill for prod DBs. Refactor commit was cosmetic only (renamed SESSION_COOKIE_NAME→SESSION_COOKIE, inlined sessionCookieOptions, trimmed multi-line comments per coding-style; -28 LOC). Remaining apps in this umbrella: inventory (BLOCKED behind PR #17 manual merge), nanodrop, dashcam, movement. --> <!-- PROGRESS 2026-05-09T22:30Z — dashcam portion DONE. PR https://gitea.bchen.dev/brendan/dashcam/pulls/2 merged at 4459450f2426720ea7f2829bd9c35e4732125748. Reviewed SHA 7011031262fa2ec7ebb334b7794bd98da9a64ce3 (feat 9b8fae4 + refactor 7011031). 72/72 vitest specs green (was 58; +14 new: 5 unit on session-renewal + 3 unit on sessions + 6 integration on session-persistence). tsc --noEmit clean. Coverage 100% on session-renewal.ts and sessions.ts. Architecture mirrors buchinese (stateful sessions DB) — not nanodrop (stateless JWT). Family constants reused verbatim (SESSION_TTL_DAYS=30, SESSION_TTL_SECONDS=2_592_000, SESSION_RENEW_THRESHOLD_SECONDS=3600); per-app SESSION_COOKIE='dashcam_session' (already in place pre-PR). Single-mint primitive issueSessionCookie in src/middleware/auth.ts; sliding renewal in src/middleware/session-renewal.ts wired into requireSession+requireApiSession (NOT loadSession); two-layer logout-resurrection defense (architectural placement + LOGOUT_PATHS URL guard with query-string strip). sessions table gained expires_at INTEGER NOT NULL DEFAULT 0 column with idempotent pragma_table_info-gated ALTER + last_seen_at-based backfill for prod DBs; getSession rejects expired rows pre-decrypt. Loaded-session call in loadSession wrapped in try/catch (tampered ciphertext -> null instead of 500). Refactor commit 7011031 cosmetic only (folded duplicate SESSION_COOKIE import into single import-with-re-export). Auto-merge via Gitea API SHA-pinned head_commit_id=7011031262fa2ec7ebb334b7794bd98da9a64ce3 (token loaded from ~/.config/tea/config.yml since ~/.git-credentials missing in this sandbox); merge_commit=4459450f2426720ea7f2829bd9c35e4732125748. Feature branch deleted post-merge. ENV_VAR_GATE clean (zero new env vars). SECURITY None — cookie attrs strictly stronger (gains Max-Age=2592000), HttpOnly+SameSite=lax+Secure+Path=/ all preserved, slide reuses existing session.id (no fixation), refresh-token rotation untouched. Manual browser smoke (close+reopen Chrome to verify cookie survives) deferred (no headed browser in autonomous loop). Remaining apps: movement (own per-project login); inventory still BLOCKED behind PR #17 manual merge. --> <!-- PROGRESS 2026-05-10T05:25Z — movement portion DONE. PR https://gitea.bchen.dev/brendan/movement/pulls/13 merged at be39c0751d5a25374e46b5f6c9d9cdb2dce8da33. Reviewed SHA 9094461752fa9eb7fb0082bd64b1719b4168e47b (feat 4f1c80f + refactor 9094461). 191/191 vitest specs green (was 175; +16 new across session-renewal.test.ts unit, auth-cookie-options.test.ts unit, session-persistence.test.ts integration). tsc --noEmit clean. Coverage on changed files ≥80% (server/constants 100%; auth.ts + session-renewal.ts 100% lines/funcs). Family constants reused verbatim (SESSION_TTL_DAYS=30, SESSION_TTL_SECONDS=2_592_000, SESSION_RENEW_THRESHOLD_SECONDS=3600); per-app SESSION_COOKIE_NAME='movement_session' (hard-cut rename from 'token' — no dual-cookie shim, mirrors nanodrop). Architecture mirrors authd/nanodrop (stateless JWT — no sessions table; renewer slides JWT itself by re-signing with fresh iat). requireAuth converted to factory makeRequireAuth(config) and threaded through auth.api.route.ts/auth.page.route.ts/admin.route.ts/static-shell-guard. Single-mint primitive issueSessionCookie consolidates server.jwt.sign + reply.setCookie at exactly 1 site (grep confirms). Two-layer logout-resurrection defense: layer 1 architectural (renewer fires only inside requireAuth, NOT in opportunistic GET /login jwtVerify), layer 2 LOGOUT_PATHS URL guard with query-string strip (/logout + /api/v1/auth/logout). sameSite='strict' preserved (stricter than family default 'lax', kept per never-relax invariant). JWT_EXPIRY env var REMOVED (env-var gate cleaner: -1 +0) from src/server/config.ts + compose.yaml + 3 test fixtures. Refactor commit 9094461 cosmetic only (narrowed fakeRequest/fakeReply types in session-renewal.test.ts so 10 `as never` casts disappeared; -20 LOC, single-file scope). Auto-merge via Gitea API POST /repos/brendan/movement/pulls + POST /merge SHA-pinned head_commit_id=9094461752fa9eb7fb0082bd64b1719b4168e47b (token from ~/.config/tea/config.yml since ~/.git-credentials missing in this sandbox); merge_commit=be39c0751d5a25374e46b5f6c9d9cdb2dce8da33. Feature branch deleted post-merge. ENV_VAR_GATE clean (zero added, one removed: JWT_EXPIRY). SECURITY None — cookie attrs strictly stronger (gains Max-Age=2_592_000), HttpOnly+SameSite=strict+Secure+Path=/ all preserved, slide reuses existing JWT claims (sub+username) so no fixation, fresh iat/exp on re-mint. Manual browser smoke (close+reopen Chrome) deferred (no headed browser). Umbrella status: only inventory remaining (BLOCKED — its session-cookie tier requires the migrate-to-authd PR #17 to land first; PR #17 is awaiting user manual-merge). --> <!-- PROGRESS 2026-05-09T17:13Z — nanodrop portion DONE. PR https://gitea.bchen.dev/brendan/nanodrop/pulls/4 merged at aed9931e143a51b000cdb924a2ac2231b6b04972. Reviewed SHA 3b3a56cd947e31f3198420fe2f90af430aaa52b7 (feat 86870db + 623a337 + 0f0c2f0; refactor a4355e1 + cbc22dc; docs 3b3a56c). 125/125 vitest specs green (was 112; +13 new: 7 unit on session-renewal + 6 integration on session-persistence). Build clean. Family constants reused verbatim (SESSION_TTL_DAYS=30, SESSION_TTL_SECONDS=2_592_000, SESSION_RENEW_THRESHOLD_SECONDS=3600); per-app SESSION_COOKIE_NAME='nanodrop_session' (renamed from 'token' — hard-cut, no dual-cookie shim, single-user deploy). Architecture mirrors authd (stateless JWT, no sessions table) since nanodrop has no session DB; renewer slides the JWT itself by re-signing with a fresh iat. requireAuth converted to factory makeRequireAuth(config). Two logout paths URL-guarded in LOGOUT_PATHS={'/logout','/api/v1/auth/logout'} with query-string strip. Opportunistic-auth blocks (GET /, GET /f/:id) call request.jwtVerify() directly outside the factory so they don't slide. Single mint primitive issueSessionCookie consolidates app.jwt.sign + reply.setCookie at exactly one site (auth.ts:29-30). sameSite='strict' preserved (stricter than family default 'lax', kept per never-relax invariant). JWT_EXPIRY env var REMOVED (env-var gate cleaner — one removed, zero added) from src/config.ts, tests/helpers/setup.ts, .env.example, docker-compose.yml, README. Cycle-1 reviewer flagged the docs-drift (JWT_EXPIRY still in .env.example/docker-compose/README despite being ignored by code) → cycle-2 commit 3b3a56c removed all three references. Cycle-2 review verdict MERGE. Auto-merge via Gitea API SHA-pinned head_commit_id=3b3a56cd... (token loaded from ~/.config/tea/config.yml since ~/.git-credentials missing in this sandbox) merge_commit=aed9931. Feature branch deleted post-merge. Manual browser smoke (close+reopen Chrome to verify cookie survives) deferred (no headed browser). Remaining apps: dashcam + movement; inventory still BLOCKED behind PR #17 manual merge. --> **In scope — all 6 apps with auth/session surfaces:** - **authd** (`~/authd`) — ✅ **DONE** (PR #7 merged 2026-05-09 at `562aca5`). 30-day sliding `token` cookie + JWT, single mint primitive `issueSessionCookie`, mfa-pending guard + logout-path guard preserved. `JWT_EXPIRY` env var dropped. - **inventory** (`~/inventory`) — **NOT yet an authd client; migration filed as the separate item above** (2026-05-08, "migrate inventory authentication to authd … both human login AND MCP"). The migration also rips out inventory's MCP-OAuth-provider surface entirely (per user direction, 2026-05-08). After that lands, inventory has exactly one cookie path — `inventory_session` set by the authd handshake — and one Bearer-token path for MCP traffic, both keyed by authd `sub`. Persistence work for inventory then becomes "authd SSO cookie + `inventory_session` cookie + a long enough `sessions`-table TTL for MCP refresh-token rotations to keep up". Sequence accordingly: do the migration first, then sweep inventory along with the other authd-SSO clients (inventory, buchinese, dashcam) for the long-Max-Age treatment. - **buchinese** (`~/buchinese`) — ✅ **DONE** (PR #3 merged 2026-05-09 at `9015328`). 30-day persistent `buchinese_session` cookie + sliding renewal at most once per hour. Family constants reused verbatim. Two-layer logout-resurrection defense (architectural + URL guard). - **nanodrop** (`~/nanodrop`) — ✅ **DONE** (PR #4 merged 2026-05-09 at `aed9931`). 30-day persistent `nanodrop_session` cookie + sliding renewal at most once per hour. Family constants reused verbatim. Stateless-JWT architecture (mirrors authd; nanodrop has no sessions table). Both logout paths URL-guarded. `JWT_EXPIRY` env var removed. - **dashcam** (`~/dashcam`) — ✅ **DONE** (PR #2 merged 2026-05-09 at `4459450`). 30-day persistent `dashcam_session` cookie + sliding renewal at most once per hour. Family constants reused verbatim. Stateful sessions-DB architecture (mirrors buchinese). sessions table gained `expires_at` column with idempotent ALTER + `last_seen_at` backfill. Two-layer logout-resurrection defense (architectural + URL guard with query-string strip). Tampered-ciphertext hardening (try/catch wrap returns null instead of 500). Single-mint primitive consolidates the cookie write at exactly one site. - **movement** (`~/movement`) — ✅ **DONE** (PR #13 merged 2026-05-10 at `be39c07`). 30-day persistent `movement_session` cookie + sliding renewal at most once per hour. Family constants reused verbatim. Stateless-JWT architecture (mirrors authd/nanodrop; movement has no sessions table). Hard-cut rename from `token` → `movement_session`. Both logout paths URL-guarded. `JWT_EXPIRY` env var removed. `sameSite='strict'` preserved (stricter than family default 'lax', kept per never-relax invariant). **Out of scope:** portman, tradebot — no auth surface (verified by survey on 2026-05-08). **What "stay signed in" means in practice (the spec):** 1. **Replace session-only cookies with persistent cookies.** Today the suspicion is that some/all of these apps set their session cookie without a `Max-Age` / `Expires` attribute, which makes the browser drop it when the window closes. Set an explicit long expiry. Recommended default across the family: **30 days, sliding (rolling)** — every authenticated request bumps the cookie's expiry forward another 30 days. So an active user effectively never gets logged out; an inactive user's session lapses 30 days after their last visit. The implementer may pick 90 days if there's a reason, but pick **one** number and use it consistently across all 6 apps so the family feels coherent. 2. **For authd-SSO clients (inventory, buchinese, …):** both layers need persistence — the **authd SSO cookie** at `auth.bchen.dev` (so re-auth on the SSO server isn't required when an app's local session lapses), AND each downstream app's own session cookie (so the app doesn't bounce through OAuth on every visit). If authd issues refresh tokens, ensure the access-token-refresh path is wired up so a still-valid refresh token avoids bouncing the user to `/login`. 3. **For own-login apps (nanodrop, dashcam, movement, …):** their single session cookie just needs the long Max-Age + sliding-renewal middleware. 4. **Security attributes preserved or strengthened, not relaxed.** Every cookie touched must keep `Secure`, `HttpOnly`, `SameSite=Lax` (or `Strict` where the app already uses it). Cookie value still rotates on login (no fixation). Idle-rolling expiry uses the existing session token; do not extend a token after sign-out. 5. **Logout still ends the session.** Explicit logout button → cookie cleared server-side AND a `Set-Cookie: <name>=; Max-Age=0` sent to the browser. "Stay signed in unless you clear your cookies" = until cookies expire OR the user actively clears them OR the user clicks logout. 6. **Server-side session record matches.** Whatever sliding expiry the cookie uses, the server-side session record (DB row, redis entry, JWT exp claim, etc.) must be aligned — otherwise the cookie says "valid for 30 more days" while the server already considers it expired and bounces the user. **Plan / approach:** 1. **Survey pass per app** — for each of the 6 repos above, locate where the session cookie is set (search for `cookie`, `Set-Cookie`, `maxAge`, `max_age`, `expires`, `session`). Note current cookie attributes and current server-side session lifetime. 2. **Pick the family-wide constant** — 30 days sliding by default. File-level constant (e.g. `SESSION_TTL_DAYS = 30`) so it's tunable per repo without hunting through the codebase. 3. **Implement per repo** — one PR per app. Each PR: (a) cookie attrs updated, (b) sliding-renewal middleware added if missing, (c) server-side session lifetime aligned, (d) logout path verified to still clear cookies + server record, (e) tests covering the new persistence + logout-still-works behavior. 4. **Order:** authd first (SSO cookie propagates), then the OAuth clients (inventory, buchinese), then the own-login apps (nanodrop, dashcam, movement). Each PR independent — no cross-repo merge dependency, but landing authd first lets you smoke-test the OAuth-client changes against a long-lived SSO cookie. 5. **Standard implementer pipeline per PR:** plan → execute → review → merge. **Acceptance:** - For each of the 6 apps: signing in, closing the browser, reopening it the next day → still signed in. (The implementer will need a smoke-test plan covering this — at minimum, set a cookie, kill the browser process, restart, hit the app, expect to land on the authed page not `/login`.) - For each of the 6 apps: clicking the logout button → bounced to login (and the cookie is gone in devtools). - For each of the 6 apps: clearing site cookies in devtools → bounced to login on next request. - For each of the 6 apps: a session that has not been touched for >TTL_DAYS → bounced to login on next request (the inactive-user lapse case). - No regression in security headers / cookie flags. `Secure`, `HttpOnly`, `SameSite` attributes preserved. - Build + tests green per repo; PRs land on `main` via the implementer pipeline. **Notes for the implementer:** - This is one umbrella ask but it splits cleanly into 6 independent PRs — do not bundle into one mega-PR. File follow-ups back into `~/features.md` if any per-app implementation hits a snag that warrants its own item. - If any of the 6 apps already has 30-day-sliding cookies, that PR is a no-op + a confirmation note in the PR description; don't churn the file just to "match" — leave it alone. - The recent `inventory PR #5` and `authd PR #6` work shows the family is converging on shared conventions; this is in the same spirit. Keep the chosen `SESSION_TTL_DAYS` constant identical across all 6 repos so future audits are trivial. Source: user via reporter chat (2026-05-08).
brendan added the feature label 2026-05-13 19:02:16 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: brendan/nanodrop#18