55 Commits

Author SHA1 Message Date
TehRiehlDeal e6cf2818f9 Merge pull request 'perf: pre-index cross-layer effects + Performance Mode toggle; v0.1.24' (#14) from feature/performance into main
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / build-images (push) Successful in 3m22s
CI / push (push) Successful in 22s
CI / test (push) Successful in 25s
CI / lint (push) Successful in 26s
CI / image-scan (push) Successful in 25s
Reviewed-on: #14
2026-05-22 14:32:39 -07:00
TehRiehlDeal e250991fe2 perf: pre-index cross-layer effects + Performance Mode toggle; v0.1.24
CI / test (push) Has been skipped
CI / build-images (push) Has been skipped
CI / push (push) Has been skipped
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / lint (push) Successful in 28s
CI / image-scan (push) Has been skipped
CI / secrets-scan (pull_request) Successful in 6s
CI / sast (pull_request) Successful in 13s
CI / build-images (pull_request) Successful in 3m28s
CI / push (pull_request) Has been skipped
CI / vuln-scan (pull_request) Successful in 15s
CI / test (pull_request) Successful in 25s
CI / lint (pull_request) Successful in 27s
CI / image-scan (pull_request) Successful in 24s
The tick engine used to walk every layer's upgrades on every buyable on
every layer per tick (O(L² × U), ~10K predicate checks/sec at 10 Hz).
Now built once per tick into a Map-backed index and read from there.
Numbers, breakdown order, and entry detail are unchanged — pinned by
new equivalence tests across production, click power, requirement, gain,
buyable, wildcard fan-out, and the three challenge debuffs.

Pairs with an opt-in display.performance_mode toggle that halves the
tick rate (10 Hz → 5 Hz) and skips cosmetic animations (resource pulse,
code-scroll backdrop, click-pop floaters) for older hardware.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:24:48 -07:00
TehRiehlDeal 89642e6a19 Merge pull request 'fix: cloud save reconciliation + 401 handling; v0.1.23' (#13) from bugfix/save-sync-and-401-handling into main
CI / test (push) Successful in 25s
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / lint (push) Successful in 28s
CI / build-images (push) Successful in 3m22s
CI / push (push) Successful in 23s
CI / image-scan (push) Successful in 26s
Reviewed-on: #13
2026-05-19 23:31:37 -07:00
TehRiehlDeal 35abc6a34e fix: cloud save reconciliation + 401 handling; v0.1.23
CI / test (push) Has been skipped
CI / secrets-scan (push) Successful in 7s
CI / lint (push) Successful in 26s
CI / secrets-scan (pull_request) Successful in 6s
CI / test (pull_request) Successful in 25s
CI / lint (pull_request) Successful in 27s
CI / image-scan (pull_request) Successful in 26s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 15s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
CI / sast (pull_request) Successful in 13s
CI / vuln-scan (pull_request) Successful in 15s
CI / build-images (pull_request) Successful in 3m21s
CI / push (pull_request) Has been skipped
Cross-device login no longer wipes progress. The boot loader now
distinguishes empty cloud from cloud-error, autosave is gated on an
explicit reconciliation step, and divergent local/cloud saves prompt the
player with a side-by-side modal instead of silently clobbering. Adds a
server-side optimistic-lock so stale clients can't overwrite newer cloud
saves even if future client code regresses. 401s with failed refresh
route to /login?reason=expired with a friendly banner and clear all
in-memory state on the way out. localStorage save key is now per-user
(`trk.save.v1.<userId>`) with a one-shot migration for the legacy key.
2026-05-19 23:24:21 -07:00
TehRiehlDeal 1496272362 fix: cross-layer production mults now multiply buyables; v0.1.22
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 12s
CI / vuln-scan (push) Successful in 14s
CI / test (push) Successful in 25s
CI / lint (push) Successful in 27s
CI / build-images (push) Successful in 3m23s
CI / image-scan (push) Successful in 25s
CI / push (push) Successful in 20s
productionBreakdown applied cross-layer 'production' multipliers
(CI/CD's ×3, commit-amend's +50%/Commit, caffeine-tolerance's
+sqrt(Cups), and the per-ticket Code bonus) BEFORE the buyable loop,
so additive buyable contributions (Intern +0.5/sec, Junior +2/sec)
were summed in AFTER the multipliers and never got tripled.

With own ×6, CI/CD ×3, and 5 Interns the player was getting
6·3 + 2.5 = 20.5/sec instead of the (6 + 2.5)·3 = 25.5/sec that "All
production ×3" implies. Same shortfall on every cross-layer mult.

Fix: reorder the breakdown so own-upgrades → buyables → cross-layer
mults → tickets bonus → preNet. Multipliers now cover the full
mult. Regression test pins Code/sec at 4.5 with 1 Intern + CI/CD
(would have been 3.5 with the old ordering).

Side effect: commit-amend / caffeine-tolerance / closed-tickets all
get slightly stronger because they now reach buyable contribution
too — which was the wording's intent the whole time.
2026-05-18 16:49:18 -07:00
TehRiehlDeal 3ace63acfb Merge pull request 'fix: Releases tab stays after spend; CI/CD upgrade actually multiplies; v0.1.20' (#12) from bugfix/release-visibility into main
CI / build-images (push) Successful in 3m24s
CI / image-scan (push) Successful in 25s
CI / push (push) Successful in 21s
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 14s
CI / vuln-scan (push) Successful in 15s
CI / test (push) Successful in 26s
CI / lint (push) Successful in 27s
Reviewed-on: #12
2026-05-18 16:33:30 -07:00
TehRiehlDeal e3dbf02c7a fix: Semantic Versioning actually boosts Commit gain; v0.1.21
CI / test (push) Has been skipped
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 11s
CI / vuln-scan (push) Successful in 15s
CI / lint (push) Successful in 25s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
CI / secrets-scan (pull_request) Successful in 6s
CI / sast (pull_request) Successful in 13s
CI / vuln-scan (pull_request) Successful in 14s
CI / test (pull_request) Successful in 25s
CI / lint (pull_request) Successful in 26s
CI / build-images (pull_request) Successful in 3m25s
CI / image-scan (pull_request) Successful in 25s
CI / push (pull_request) Has been skipped
The Semantic Versioning Releases upgrade advertised +sqrt(R) to Commit
gain but had no `affects` block — same shape as the v0.1.20 CI/CD bug.
Without affects the upgrade was treated as an own-layer (Releases)
multiplier, and Releases has no production or gain to multiply.

Adds a new cross-layer `affects.target = "gain"` that mirrors the
existing `requirement` modifier. requirement cheapens the formula
input; gain multiplies the formula output. Applied AFTER the gain
formula and floored so prestige currencies stay integer.

Wires semantic-versioning as `affects: { layer: "commits", target:
"gain", scaling: "add" }`, which evaluates to `1 + sqrt(R)` — same
curve caffeine-tolerance uses for Coffee → Code, so the multiplier
shape is recognisable.

Updates nextPrestigeThreshold to divide the projected next-tier amount
by gainMult, keeping the git commit tooltip honest when the upgrade
is owned.
2026-05-18 16:23:46 -07:00
TehRiehlDeal 02cfb92b0d fix: Releases tab stays after spend; CI/CD upgrade actually multiplies; v0.1.20
Two Releases-layer bugs:

1. v0.1.19 made the tab sticky on `amountAtLeast(releases, 1)`, but
   buying CI/CD costs 1 Release Tag and drops the amount back to 0,
   re-hiding the tab. `total` only ever increments, so
   `totalAtLeast(releases, 1)` keeps the tab visible as long as the
   player has ever earned a release (equivalent in practice to
   "has ever bought a Release upgrade", since one implies the other).

2. The CI/CD Pipeline upgrade advertised "All production ×3" but did
   nothing — without an `affects` block, the tick engine treats the
   upgrade as own-layer (releases), and Releases has no `production`
   formula to multiply. Declared `affects: { layer: "*", target:
   "production", scaling: "mult" }` so the existing wildcard cross-
   layer path picks it up. Inspector now surfaces a `Releases.CI/CD
   Pipeline ×3.00` row for Code / Commits / Coffee.
2026-05-18 16:18:07 -07:00
TehRiehlDeal 612d37e4aa fix: Releases tab sticky after first release; v0.1.19
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 15s
CI / test (push) Successful in 24s
CI / lint (push) Successful in 26s
CI / secrets-scan (push) Successful in 5s
CI / build-images (push) Successful in 3m23s
CI / image-scan (push) Successful in 25s
CI / push (push) Successful in 22s
Same shape as v0.1.11 (Commits). Unlock predicate was
`amountAtLeast(tickets, 3)`, but the Releases prestige wipes tickets
in its resetsLayers — so cutting your first release immediately dropped
tickets to 0 and hid the tab. If the player closed the layer without
banking CI/CD first, there was no path back in.

Fix: anyOf(amountAtLeast(tickets, 3), amountAtLeast(releases, 1)) —
sticky once the player has at least one Release Tag, which (unlike
tickets) is never reset by anything currently in the game.
2026-05-18 16:06:27 -07:00
TehRiehlDeal 0440842a86 Merge pull request 'feat: OG/Twitter Card metadata for chat link previews; v0.1.18' (#11) from feature/link-prettier into main
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 14s
CI / vuln-scan (push) Successful in 15s
CI / test (push) Successful in 26s
CI / lint (push) Successful in 28s
CI / build-images (push) Successful in 3m23s
CI / image-scan (push) Successful in 25s
CI / push (push) Successful in 21s
Reviewed-on: #11
2026-05-18 16:00:34 -07:00
TehRiehlDeal 4359955c51 feat: OG/Twitter Card metadata for chat link previews; v0.1.18
CI / test (push) Has been skipped
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / lint (push) Successful in 26s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
CI / secrets-scan (pull_request) Successful in 6s
CI / sast (pull_request) Successful in 14s
CI / vuln-scan (pull_request) Successful in 15s
CI / test (pull_request) Successful in 25s
CI / lint (pull_request) Successful in 27s
CI / build-images (pull_request) Successful in 5m7s
CI / image-scan (pull_request) Successful in 24s
CI / push (pull_request) Has been skipped
index.html grows the Open Graph and Twitter Card meta tags (title,
description, type, site_name, url, image + dimensions, alt) plus a
plain `<meta name="description">` for SEO. Same copy as the README
one-liner so every surface stays in sync.

Absolute URLs are required for Facebook+Twitter and strongly preferred
elsewhere, but the deployment URL isn't known at build time. Solved with
the same pattern config.js already uses: a `__OG_BASE_URL__` token in
index.html gets sed-substituted by the nginx entrypoint from a new
`APP_PUBLIC_URL` env var. Trailing-slash trim and sed-meta escaping are
both handled. Unset env = relative URLs (Slack/Discord/iMessage still
render fine, Facebook won't).

README gains a paragraph under Runtime Config documenting the new var.
2026-05-18 15:49:38 -07:00
TehRiehlDeal 348a6f65ea Merge pull request 'feat: closed tickets buff Code; totals strip moves to MenuBar; v0.1.17' (#10) from refactor/ticket-benefits into main
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 16s
CI / test (push) Successful in 26s
CI / lint (push) Successful in 27s
CI / build-images (push) Successful in 3m22s
CI / image-scan (push) Successful in 26s
CI / push (push) Successful in 33s
Reviewed-on: #10
2026-05-18 15:21:00 -07:00
TehRiehlDeal cee7e375f9 feat: closed tickets buff Code; totals strip moves to MenuBar; v0.1.17
CI / test (push) Has been skipped
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 12s
CI / vuln-scan (push) Successful in 15s
CI / lint (push) Successful in 24s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
CI / secrets-scan (pull_request) Successful in 5s
CI / build-images (pull_request) Has been cancelled
CI / image-scan (pull_request) Has been cancelled
CI / push (pull_request) Has been cancelled
CI / sast (pull_request) Has been cancelled
CI / vuln-scan (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
Two related changes:

Closed tickets are now mechanically rewarding. Each ticket adds +25% to
Code production (additive), so 1 ticket = ×1.25, 10 = ×3.5, stacking
with own-layer + commit-amend + coffee multipliers. Shows in the
production inspector as `Tickets.N closed  ×1.X` with `+25% Code/sec
per ticket` underneath, so the player can see why their /sec jumped
the moment a P0 Bug closes. InfoModal copy updated to advertise the
mechanic; stale `ticket-master` milestone description (which promised
a separate +50% at 10 tickets that was never implemented) replaced
with text reflecting the per-ticket bonus.

Totals strip moved from the resource hero strip into the top MenuBar —
the previous placement was a misread of "top bar". Restored the hero
strip's original single-headline layout. The strip is now compact
inline `[symbol] [amount]` chips sized for the 28px MenuBar row, sitting
between the breadcrumb and the save/info/debug buttons.
2026-05-18 15:17:03 -07:00
TehRiehlDeal 8ac1bd793d Merge pull request 'fix: Tickets tab stays put during active challenge; v0.1.14' (#9) from bugfix/tickets into main
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 14s
CI / vuln-scan (push) Successful in 15s
CI / lint (push) Successful in 26s
CI / test (push) Successful in 27s
CI / build-images (push) Successful in 3m23s
CI / image-scan (push) Successful in 26s
CI / push (push) Successful in 21s
Reviewed-on: #9
2026-05-18 15:01:32 -07:00
TehRiehlDeal 5c3bbb99b6 feat: always-on layer-totals strip + click rate limit; v0.1.16
CI / test (push) Has been skipped
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / lint (push) Successful in 25s
CI / build-images (push) Has been skipped
CI / secrets-scan (push) Successful in 6s
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
CI / secrets-scan (pull_request) Successful in 6s
CI / sast (pull_request) Successful in 14s
CI / vuln-scan (pull_request) Successful in 15s
CI / test (pull_request) Successful in 24s
CI / lint (pull_request) Successful in 26s
CI / build-images (pull_request) Successful in 3m22s
CI / image-scan (pull_request) Successful in 26s
CI / push (pull_request) Has been skipped
Top bar gets a compact running-totals strip on the left — one chip per
unlocked layer with current amount, tinted in the layer's color, and
click-to-focus. Sits beside the existing headline so you can read every
resource without flipping tabs.

ClickButton enforces a 10/s cap on accepted clicks (100ms min interval,
measured with performance.now()). Above sustainable human speed so real
players don't notice; an autoclicker at 50-200 Hz gets throttled to the
same 10/s. Silent by design — a visible "throttled" indicator would
just teach the cap.
2026-05-18 14:52:30 -07:00
TehRiehlDeal c320a77bd9 feat: prestige tooltip shows next-tier threshold; v0.1.15
The git commit (and git tag -a) tooltip now shows the upstream amount
needed to bump the gain by one tier — "next +2 ★  20K LoC (8K more)"
— so players don't have to back-solve prestigePoints by hand.

Closed-form inverse from prestigePoints(req, pow): the next-integer
gain G+1 needs amount ≥ (G+1)^(1/pow) · req · reqMult, so any
requirement-modifier upgrade (force-push) flows through automatically.
Bails out for unknown formulas instead of guessing.
2026-05-18 14:32:53 -07:00
TehRiehlDeal 956d1476bc fix: Tickets tab stays put during active challenge; v0.1.14
Entering a Tickets challenge wipes Commits to 0, and goals like P0 Bug
(reach 1M LoC) intentionally require commit prestiges to ramp back up.
But each commit reset Commits to 1, which fails Tickets' `commits >= 5`
predicate — so the tab vanished mid-run and the player had no Exit
button to escape the challenge they were still inside.

Fix: LeftRail treats the layer hosting the active challenge as unlocked
regardless of its predicate. Surgical one-spot override; the underlying
unlock condition still gates first-time access.
2026-05-18 14:27:56 -07:00
TehRiehlDeal d6813c9182 Merge pull request 'fix: Commits tab sticky after prestige; Coffee unlock fires; v0.1.11' (#8) from bugfix/commit-display-and-tickets into main
CI / secrets-scan (push) Successful in 5s
CI / build-images (push) Successful in 3m25s
CI / image-scan (push) Successful in 24s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / test (push) Successful in 24s
CI / lint (push) Successful in 28s
CI / push (push) Successful in 21s
Reviewed-on: #8
2026-05-18 13:25:13 -07:00
TehRiehlDeal 720fc29fc0 Added autoComplete to props
CI / test (push) Has been skipped
CI / secrets-scan (push) Successful in 8s
CI / sast (push) Successful in 17s
CI / vuln-scan (push) Successful in 19s
CI / secrets-scan (pull_request) Successful in 5s
CI / test (pull_request) Successful in 30s
CI / lint (push) Successful in 31s
CI / sast (pull_request) Successful in 10s
CI / vuln-scan (pull_request) Successful in 14s
CI / build-images (push) Has been skipped
CI / lint (pull_request) Successful in 28s
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
CI / image-scan (pull_request) Successful in 24s
CI / build-images (pull_request) Successful in 4m25s
CI / push (pull_request) Has been skipped
2026-05-18 13:18:40 -07:00
TehRiehlDeal acf3771a70 fix: signup password field gets autoComplete; v0.1.13
CI / test (push) Has been skipped
CI / secrets-scan (push) Successful in 5s
CI / secrets-scan (pull_request) Successful in 8s
CI / sast (push) Successful in 14s
CI / vuln-scan (push) Successful in 15s
CI / test (pull_request) Successful in 26s
CI / lint (push) Successful in 29s
CI / sast (pull_request) Successful in 14s
CI / vuln-scan (pull_request) Successful in 15s
CI / lint (pull_request) Successful in 30s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
CI / build-images (pull_request) Successful in 4m54s
CI / image-scan (pull_request) Successful in 25s
CI / push (pull_request) Has been skipped
Signup.tsx already passed `autoComplete="new-password"` on the password
Field, but the local Field wrapper's typed props didn't list the key, so
the destructured `...rest` silently dropped it before reaching the input.
Password managers therefore didn't see a new-password field to suggest
into. Login's Field already had the right shape — mirrored it here.
2026-05-18 13:16:26 -07:00
TehRiehlDeal f2342722f8 feat: active-challenge runtime for the Tickets layer; v0.1.12
CI / test (push) Has been skipped
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / lint (push) Successful in 25s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
CI / secrets-scan (pull_request) Successful in 5s
CI / sast (pull_request) Successful in 13s
CI / vuln-scan (pull_request) Successful in 14s
CI / test (pull_request) Successful in 25s
CI / lint (pull_request) Successful in 25s
CI / build-images (pull_request) Successful in 3m25s
CI / image-scan (pull_request) Successful in 25s
CI / push (pull_request) Has been skipped
Tickets challenges used to be read-only preview cards — the runtime was
acknowledged-missing in the info modal. Now fully wired:

- gameSlice owns the active challenge (layerId, challengeId, seed).
  Serialized so it survives a reload mid-run.
- New challenges.ts thunks: enterChallenge wipes Code/Commits/Tech Debt
  per Challenge.resetsLayers, exitChallenge wipes again with no reward,
  completeChallenge awards the reward + bumps the completion counter and
  re-runs the milestone evaluator, evaluateActiveChallenge checks the goal
  predicate each tick.
- Tick engine threads ChallengeMods (debuffs + seed) through every
  production / cost / click calc, so the headline /sec matches what
  actually accumulates mid-challenge. Same productionBreakdown that powers
  the debug inspector picks up debuff lines too.
- Debuffs implemented: P0 Bug halves Code production; Legacy Codebase
  silences buyables (effect labels marked "(disabled this run)"); Flaky
  Test jitters owned upgrade effectiveness with a seeded per-upgrade
  factor in [0.5, 1.5] that's stable within the run so the display
  doesn't strobe.
- ChallengeCard grows Enter / Exit buttons, an "active" pill, and
  dimmed-when-another-is-running state. InfoModal drops the preview
  badge and the "Blocked on the Tickets runtime" line on Releases.
- Schema: Challenge gains optional resetsLayers; defaults to the owning
  layer's branches.
2026-05-18 12:53:10 -07:00
TehRiehlDeal 20ef25daff fix: Commits tab sticky after prestige; Coffee unlock fires; v0.1.11
Commits unlock was `bestAtLeast(code,5000)`, which v0.1.6 deliberately
wipes on prestige — so the tab hid itself the moment you used it. New
`anyOf`/`allOf` predicate combinators let the gate be "first-time LoC
threshold OR any commits ever held", which sticks across the reset.

Coffee's `hasMilestone(commits, first-commit)` predicate was correct,
but nothing in the runtime ever dispatched `completeMilestone` — the
reducer was dead code. Added `evaluateMilestones` that runs each tick
and after prestige, awarding any layer milestone whose requirement
flipped true. Idempotent; covers all future milestone gates too.
2026-05-18 12:43:07 -07:00
TehRiehlDeal de8cdabd66 Merge pull request 'feat: debug-mode production breakdown inspector; v0.1.10' (#6) from feature/generation-info into main
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 14s
CI / vuln-scan (push) Successful in 16s
CI / test (push) Successful in 28s
CI / lint (push) Successful in 29s
CI / build-images (push) Successful in 3m23s
CI / image-scan (push) Successful in 25s
CI / push (push) Successful in 21s
Reviewed-on: #6
2026-05-18 11:33:35 -07:00
TehRiehlDeal 2f50267e34 feat: debug-mode production breakdown inspector; v0.1.10
CI / test (push) Has been skipped
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 12s
CI / vuln-scan (push) Successful in 15s
CI / image-scan (push) Has been skipped
CI / lint (push) Successful in 25s
CI / build-images (push) Has been skipped
CI / secrets-scan (pull_request) Successful in 5s
CI / push (push) Has been skipped
CI / sast (pull_request) Successful in 13s
CI / vuln-scan (pull_request) Successful in 15s
CI / test (pull_request) Successful in 24s
CI / lint (pull_request) Successful in 26s
CI / image-scan (pull_request) Has been cancelled
CI / push (pull_request) Has been cancelled
CI / build-images (pull_request) Has been cancelled
Adds a `debug` toggle in the menu bar that makes values inspectable.
With it on, hover the headline +X/s rate to see every contribution
to it grouped by source — own-layer multipliers, cross-layer
multipliers with their scaling rule and current owner amount,
additive buyables, and the Tech Debt penalty if active.

Refactors `computeProductionPerSec` to delegate to a new
`productionBreakdown` walk so the headline number, the tick reducer,
and the inspector tooltip all read from one source of truth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 11:30:11 -07:00
TehRiehlDeal b2a9d7aa30 Merge pull request 'feat: manual click button + wipe-save fix; v0.1.9' (#5) from feature/clicking into main
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 14s
CI / vuln-scan (push) Successful in 15s
CI / test (push) Successful in 25s
CI / lint (push) Successful in 26s
CI / build-images (push) Successful in 3m23s
CI / image-scan (push) Successful in 26s
CI / push (push) Successful in 20s
Reviewed-on: #5
2026-05-18 10:52:58 -07:00
TehRiehlDeal 2782c64102 feat: manual click button + wipe-save fix; v0.1.9
CI / test (push) Has been skipped
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 11s
CI / vuln-scan (push) Successful in 14s
CI / lint (push) Successful in 24s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
CI / secrets-scan (pull_request) Successful in 6s
CI / sast (pull_request) Successful in 14s
CI / vuln-scan (pull_request) Successful in 15s
CI / test (pull_request) Successful in 24s
CI / lint (pull_request) Successful in 26s
CI / build-images (pull_request) Successful in 3m24s
CI / image-scan (pull_request) Successful in 25s
CI / push (pull_request) Has been skipped
Wipe-save was leaving the game in a zombie state: SettingsModal wrote
`layers: {}` to the cloud, and on reload `replaceLayers({})` clobbered
the slice's freshly-initialized layer state. The tick reducer then
silently no-op'd because `state.layers['code']` was undefined while
the headline still computed per-sec from content.

`replaceAll` in the layers slice now hydrates any missing content
layer back to its default `startData`. Side effect: old saves missing
a newly-added layer get default state on load, so future content
additions are forward-compatible.

Also adds a clicker button on layers that define `clickPower`. Click
value flows through the same cross-layer multiplier pipeline as
production, so new click-efficiency upgrades (Mechanical Keyboard,
Caffeinated Typing) compose cleanly. Tech Debt penalty intentionally
NOT applied to clicks — clicking is the player's escape hatch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 10:42:43 -07:00
TehRiehlDeal 0b4f227047 Merge pull request 'fix: prestige resets Tech Debt; favicon; v0.1.8' (#4) from bugfix/scaling-pacing into main
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / test (push) Successful in 24s
CI / lint (push) Successful in 25s
CI / build-images (push) Successful in 2m44s
CI / image-scan (push) Successful in 22s
CI / push (push) Successful in 20s
Reviewed-on: #4
2026-05-18 10:21:36 -07:00
TehRiehlDeal 232c5a1c1d fix: prestige resets Tech Debt; favicon; v0.1.8
CI / test (push) Has been skipped
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 11s
CI / vuln-scan (push) Successful in 13s
CI / lint (push) Successful in 25s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
CI / secrets-scan (pull_request) Successful in 4s
CI / sast (pull_request) Successful in 11s
CI / vuln-scan (pull_request) Successful in 14s
CI / test (pull_request) Successful in 24s
CI / lint (pull_request) Successful in 26s
CI / build-images (pull_request) Successful in 3m15s
CI / image-scan (pull_request) Successful in 23s
CI / push (pull_request) Has been skipped
Commit/Releases prestige now wipe Tech Debt along with the other
upstream layers. Without this, the v0.1.7 global debt penalty
survived prestige and silently divided post-prestige production,
making commit upgrades look broken.

Also wires up the new TehRiehlIncremental.png as the browser favicon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 10:11:08 -07:00
TehRiehlDeal 2511e9e97a Merge pull request 'feat: prestige reads current LoC; tiered CI; v0.1.6' (#3) from bugfix/scaling-pacing into main
CI / secrets-scan (push) Successful in 7s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / test (push) Successful in 24s
CI / lint (push) Successful in 26s
CI / build-images (push) Successful in 3m20s
CI / image-scan (push) Successful in 26s
CI / push (push) Successful in 21s
Reviewed-on: #3
2026-05-17 23:05:27 -07:00
TehRiehlDeal 3202275dd6 feat: info modal + Tech Debt redesign; v0.1.7
CI / test (push) Has been skipped
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 15s
CI / lint (push) Successful in 25s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
CI / secrets-scan (pull_request) Successful in 6s
CI / sast (pull_request) Successful in 13s
CI / vuln-scan (pull_request) Successful in 14s
CI / test (pull_request) Successful in 24s
CI / lint (pull_request) Successful in 26s
CI / build-images (pull_request) Successful in 3m22s
CI / image-scan (pull_request) Successful in 26s
CI / push (pull_request) Has been skipped
Info modal
  - New BookOpen button in the top menu bar (between breadcrumb and
    save) opens ~/MANUAL.md — a per-layer overview. Hand-authored copy
    in InfoModal.tsx, keyed by layer.id, with status badges for layers
    that aren't fully wired yet (Tickets: preview, Releases: unreachable).
  - uiSlice gains infoOpen state + setInfoOpen reducer.

Tech Debt — actually a mechanic now
  - Global production penalty: Code/sec ÷ (1 + debt/500). Every
    production-bearing layer except techdebt is divided by this. 500 Δ
    halves production, 5,000 Δ cuts it 10×. Debt can no longer be
    ignored — it actively slows everything else down.
  - Pay-down rebalanced from additive (-0.1 per buy, useless after 10)
    to multiplicative (×0.9 per buy via the new cumulative-mult applyMode).
    Asymptotic — never reaches zero, so the player keeps managing it.
  - Schema: Buyable.applyMode: 'additive' | 'cumulative-mult'. The
    tick engine switches between mult += effect(count) and
    mult *= effect(0)^count based on this flag.
  - RightPanel's BuyableCard rendering: cumulative-mult buyables show
    "×0.90/buy · now ×0.122" instead of a nonsensical "+X/s" delta.

Both package versions bumped to 0.1.7; patch notes entry added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 22:57:12 -07:00
TehRiehlDeal 7d3476f946 feat: prestige reads current LoC; tiered CI; v0.1.6
Prestige loop
  - prestigeGain + canPrestige now read state.layers[primary].amount instead
    of .best. The threshold is "do you currently hold N LoC", not "have you
    ever held N LoC", and the gain reflects what's actually being traded.
  - resetLayer wipes best and total too. Each prestige is a fresh slate;
    nothing rides through to bias the next run's gain calculation.
  - PrestigeButton tooltip: "current/threshold" line and explanatory copy
    now reference current upstream amount.

CI
  - Tiered gating via job-level `if:`. Non-main branch pushes run only the
    pre-build set (lint, secrets-scan, vuln-scan, sast). Pull requests AND
    main pushes additionally run test + build-images + image-scan. Main
    pushes alone run the Harbor push + cosign + per-package git tags.
  - `needs:` cascades skipped upstream jobs to dependents, so we don't
    duplicate the condition on every downstream job.

Both package versions bumped to 0.1.6; patch notes entry added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 22:44:35 -07:00
TehRiehlDeal 5477c0be4c Merge pull request 'feat: balance pass, layer wiring, reset save, modal fixes; v0.1.5' (#2) from bugfix/scaling-pacing into main
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / test (push) Successful in 24s
CI / lint (push) Successful in 26s
CI / build-images (push) Successful in 3m30s
CI / image-scan (push) Successful in 25s
CI / push (push) Successful in 23s
Reviewed-on: #2
2026-05-17 20:21:08 -07:00
TehRiehlDeal 0c79235c72 feat: balance pass, layer wiring, reset save, modal fixes; v0.1.5
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / test (push) Successful in 24s
CI / lint (push) Successful in 26s
CI / build-images (push) Successful in 3m24s
CI / image-scan (push) Successful in 24s
CI / push (push) Has been skipped
CI / secrets-scan (pull_request) Successful in 5s
CI / sast (pull_request) Successful in 13s
CI / vuln-scan (pull_request) Successful in 15s
CI / test (pull_request) Successful in 25s
CI / lint (pull_request) Successful in 26s
CI / build-images (pull_request) Successful in 3m23s
CI / image-scan (pull_request) Successful in 25s
CI / push (pull_request) Has been skipped
Scaling
  - commit-amend's exponential 1.5^commits is gone — replaced with linear
    +50%/commit via a new addPerOwnAmount scaling mode. Past a few hundred
    commits the old curve overflowed to Infinity and trivialized the game.
  - schema's affects.scaling is now a closed enum: 'mult' / 'addPerOwnAmount'
    / 'add'. The old 'powOwnAmount' (and its uncommented 'flat') are out.
  - affects.layer: '*' wildcard added for "all production" effects (techdebt
    excluded so it can't become a self-inflicted penalty).
  - computeProductionPerSec() exported from tick-engine. Game.tsx uses it
    for the headline readout, so the displayed LoC/sec is the SAME number
    the tick loop credits — cross-layer multipliers included.
  - Infinity/NaN clamp in computeProductionPerSec — degenerate combos can't
    accumulate non-finite state any more.

Layer wiring
  - coffee.caffeine-tolerance now actually adds sqrt(Cups) to Code production.
  - coffee.third-wave actually doubles the Espresso shot's per-unit rate.
  - rebase + force-push moved to explicit 'mult' scaling for clarity.

UX
  - UpgradeCard renders the upgrade's rule AND current contribution for
    cross-layer upgrades — "+50%/Commit · now x4.5" instead of a flat "x1.50"
    that lied about the live value.
  - BuyableCard's "+X/s" now reflects per-buyable boosts from other layers.
  - Settings has a save.reset row: two-click confirm, wipes local storage,
    PUTs an empty blob to cloud, hard-reloads. Recovers from a corrupt save.
  - Dialog centering fixed — the open animation was overwriting the
    -translate-x/y centering transform via its own translate keyframe. Added
    an opacity-only 'fade-in' animation, switched DialogContent + Overlay
    to use it.
  - DialogBody gets flex-1 min-h-0 so long content (patch notes) scrolls
    inside the fixed-height dialog instead of pushing it past the viewport.

Deferred
  - Active-challenge runtime (debuffs/rewards). Releases reachability
    depends on this.
  - releases.ci-cd 'All production x3' is mechanically wired (via '*') but
    can't fire until Releases is reachable.
  - Milestone effectTags still don't drive behavior.

Both package versions bumped to 0.1.5; patch notes entry added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:01:13 -07:00
TehRiehlDeal 2ae75da5f9 Merge pull request 'feat: cross-layer upgrades, prestige tooltip, ticket previews; v0.1.4' (#1) from bugfix/multipliers into main
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / test (push) Successful in 24s
CI / lint (push) Successful in 26s
CI / push (push) Successful in 22s
CI / build-images (push) Successful in 3m23s
CI / image-scan (push) Successful in 25s
Reviewed-on: #1
2026-05-17 19:30:59 -07:00
TehRiehlDeal f7b43602da chore: prettier --write to match repo-root format config
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 15s
CI / test (push) Successful in 24s
CI / lint (push) Successful in 26s
CI / build-images (push) Successful in 3m25s
CI / secrets-scan (pull_request) Successful in 6s
CI / sast (pull_request) Successful in 16s
CI / vuln-scan (pull_request) Successful in 17s
CI / image-scan (push) Successful in 23s
CI / test (pull_request) Successful in 26s
CI / lint (pull_request) Successful in 27s
CI / push (push) Has been skipped
CI / build-images (pull_request) Successful in 3m24s
CI / image-scan (pull_request) Successful in 25s
CI / push (pull_request) Has been skipped
Targeted prettier --check on the same files passed locally but root
pnpm format:check (what CI runs) failed on different printWidth rules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:20:10 -07:00
TehRiehlDeal 3d7d73ee6a feat: cross-layer upgrades, prestige tooltip, ticket previews; v0.1.4
CI / secrets-scan (push) Successful in 5s
CI / vuln-scan (push) Successful in 16s
CI / sast (push) Successful in 18s
CI / test (push) Successful in 25s
CI / lint (push) Failing after 26s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
Three player-visible fixes plus the schema work behind them.

Schema: new optional `affects` field on Upgrade lets an upgrade target a
different layer than its home (production / requirement / buyable), with
a powOwnAmount scaling for the "x1.5 per Commit owned" pattern and a
single-upgrade `requires` guard for conditional bonuses.

Wiring:
  - tick-engine pulls cross-layer production + buyable multipliers via a
    new crossLayerMultiplier() that walks every layer's upgrades.
  - prestige.ts gains effectiveRequirement() and divides the gainFormula
    input by the requirement multiplier, so a reduced threshold doesn't
    leave the player in a "can prestige but gain is 0" window.
  - commits.json: commit-amend / rebase / force-push gain matching
    `affects` entries — they finally apply the bonuses their descriptions
    promise.

UX:
  - TooltipProvider mounted at the app root with delayDuration=0 so all
    Radix tooltips appear instantly. PrestigeButton swaps its native
    title= for a rich Tooltip showing gain, threshold, current best, and
    any upgrade-modified requirement.
  - RightPanel now renders a Challenges section using a new ChallengeCard
    component so the Tickets layer isn't a blank panel. Cards are marked
    "preview" since the active-challenge runtime is still future work.

Patch notes: retroactive entries for 0.1.1 through 0.1.3, plus the
current 0.1.4 entry. Future commits should keep this in sync.

Both package versions bumped to 0.1.4 to stay in lockstep for the
Harbor push job.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:17:13 -07:00
TehRiehlDeal b442f5eb15 chore(server): bump to v0.1.3 to track client release
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 14s
CI / vuln-scan (push) Successful in 15s
CI / test (push) Successful in 24s
CI / lint (push) Successful in 26s
CI / build-images (push) Successful in 3m21s
CI / image-scan (push) Successful in 25s
CI / push (push) Successful in 20s
Keeps the client and server image tags in lockstep so v0.1.3 means
the same release on both sides of the Harbor push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 18:48:04 -07:00
TehRiehlDeal 082f2fca3f feat(client): manual save via IDE-style top menu bar; bump v0.1.3
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 12s
CI / vuln-scan (push) Successful in 15s
CI / test (push) Successful in 24s
CI / lint (push) Successful in 26s
CI / image-scan (push) Has been cancelled
CI / push (push) Has been cancelled
CI / build-images (push) Has been cancelled
Adds a 28px menu bar above the resource hero strip. Left edge is a
faux file-path breadcrumb (~/tehriehl-incremental/layers/<id>.ts) to
reinforce the "game lives inside an editor" concept; right edge is
a Save button with the floppy icon and a Ctrl/Cmd+S shortcut hint.
Button cycles idle -> saving -> saved (1.5s) with a cloud-failed
state when only the local write lands.

saveNow() in save-manager bypasses the autosave throttling and
returns per-target outcome so the button can give honest feedback
without conflating a cloud blip with a full save failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 18:42:28 -07:00
TehRiehlDeal c47a15f248 fix(client): gate autosave on boot-complete; bump v0.1.2
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / test (push) Successful in 24s
CI / lint (push) Successful in 26s
CI / build-images (push) Successful in 3m22s
CI / image-scan (push) Successful in 26s
CI / push (push) Successful in 20s
Autosave middleware fired the moment auth landed because lastLocal and
lastCloud were both 0, so any current timestamp tripped the threshold.
The empty initial state then raced the boot effect's loadCloud() and
sometimes won, overwriting an existing save with zeros before the load
could replace state.

Added a `game.loaded` flag — false at boot, flipped to true after the
load attempt finishes (success or 404). The autosave gate now refuses
to write while loaded is false, and replaceAll() forces loaded back to
false so a state restore can't accidentally leave it set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 18:25:55 -07:00
TehRiehlDeal 9ee4869042 feat(server): self-applying migrations on container start; bump v0.1.1
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / test (push) Successful in 26s
CI / lint (push) Successful in 27s
CI / build-images (push) Successful in 3m38s
CI / image-scan (push) Successful in 24s
CI / push (push) Successful in 22s
Three coordinated changes so the deployed image is operationally
ready to roll into prod:

1. Keep Prisma CLI in the production bundle. Moved `prisma` from
   devDependencies into dependencies in packages/server/package.json
   so `pnpm deploy --prod` includes it. Version pin matches
   @prisma/client (^5.20.0) so the CLI's bundled engine and the
   client's stay aligned.

2. Container entrypoint that runs migrations before booting.
   Added packages/server/docker/entrypoint.sh: it invokes
   `/app/node_modules/.bin/prisma migrate deploy` against the
   live DATABASE_URL, then `exec "$@"` drops into the CMD
   (node dist/main.js). `migrate deploy` is the production-safe
   verb — only applies committed migrations from prisma/migrations,
   never generates new ones, idempotent on already-applied state.

   Invoked through tini in ENTRYPOINT so SIGTERM still flows
   cleanly to Node when the container is stopped. Uses the
   `.bin/prisma` symlink directly because we deleted npm/npx
   from the runtime image earlier (for the trivy gate).

   DATABASE_URL is redacted in the entrypoint's log line so the
   credentials don't end up in container logs.

3. Version bump: both packages 0.1.0 → 0.1.1 so Harbor's
   "refuse to overwrite existing tag" check accepts the push.

Verification:
- prisma CLI present at /app/node_modules/.bin/prisma in the
  runtime image.
- Empty DB boot: entrypoint applies 20260517191555_init,
  creates users + saves + _prisma_migrations tables, server
  reaches "listening", /api/health returns ok.
- Already-applied DB boot: entrypoint logs "No pending
  migrations to apply", server boots, no-op as expected.
- 80 unit tests + 7 integration tests pass.
- Trivy: 0 new findings; only the pre-existing lodash CVE
  remains (already in .trivyignore).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 18:07:11 -07:00
TehRiehlDeal 781356a254 fix(ci): install trivy as binary; add AGPL-3.0 LICENSE
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 14s
CI / vuln-scan (push) Successful in 15s
CI / test (push) Successful in 24s
CI / lint (push) Successful in 26s
CI / build-images (push) Successful in 3m18s
CI / image-scan (push) Successful in 26s
CI / push (push) Successful in 1m2s
Two unrelated changes that landed together:

1. Trivy image-scan was failing with "ignore file not found:
   /src/.trivyignore" — classic docker-in-docker mount issue.
   The runner's job container has the workspace at
   /workspace/<owner>/<repo>, but `docker run -v "$PWD:/src"`
   goes through the docker socket, where the daemon (running on
   the host) tries to mount /workspace/... — which only exists
   inside the job container, not on the host. The mount
   silently creates an empty dir, so trivy can't find the file.

   Install trivy as a binary on the runner directly and invoke
   it without docker. Now `.trivyignore` resolves against the
   workspace cwd. Applies to both server and client image scans
   plus both SBOM steps for consistency.

2. License added: AGPL-3.0-or-later. Restricts forks (including
   network/SaaS deployments) to also be AGPL — recognized as
   open source but with a meaningful copyleft. SPDX identifier
   set in root package.json. Canonical FSF text in LICENSE.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 17:06:25 -07:00
TehRiehlDeal efd073ebe1 fix(ci): pass trivy image scans
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 15s
CI / build-images (push) Successful in 3m22s
CI / test (push) Successful in 26s
CI / lint (push) Successful in 28s
CI / push (push) Has been skipped
CI / image-scan (push) Failing after 16s
Three independent issues caused 35 + 15 HIGH/CRITICAL findings:

1. Client base — nginx:1.27-alpine pulled Alpine 3.21.3, which
   carries old libcrypto3/libssl3/libpng/libxml2/musl/nghttp2/zlib.
   Bumped to nginx:1.29-alpine and added `apk update && apk
   upgrade --no-cache` so the layer pulls every available patch
   in the current Alpine repo. Verified: 0 findings.

2. Server runtime — npm/yarn/corepack ship bundled inside the
   node:20-alpine image at /usr/local/lib/node_modules/npm/...
   They drag in cross-spawn, glob, minimatch, tar, and friends —
   all of which had HIGH CVEs. We never invoke them at runtime
   (CMD is `node dist/main.js`); deleting them in the runtime
   stage cuts 11 of the 15 findings. Also added `apk upgrade`
   for parity with the client.

3. Multer transitive — @nestjs/platform-express pulls in
   multer 2.0.2 which has three HIGH CVEs (fixed in 2.1.1).
   Added a pnpm `overrides` entry to pin it.

For the remaining lodash CVE-2026-4800: Trivy lists 4.18.0 as
the fixed version, but that release isn't published on npm. Added
.trivyignore + wired --ignorefile into the trivy invocations,
with a comment explaining why and when to re-evaluate.

Verification (local builds + trivy scans):
- teh-riehl-server: 0 HIGH/CRITICAL across all targets.
- teh-riehl-client: 0 vulnerabilities (alpine 3.23.4).
- Server boots, /api/health returns ok.
- Client serves /config.js with APP_API_BASE_URL override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 16:54:28 -07:00
TehRiehlDeal d31b2c6dc7 fix(ci): use postgres service name as DATABASE_URL host
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 15s
CI / build-images (push) Successful in 3m22s
CI / lint (push) Successful in 26s
CI / test (push) Successful in 26s
CI / image-scan (push) Failing after 50s
CI / push (push) Has been skipped
Gitea Actions runs jobs in their own Docker network, so service
containers are reachable by their YAML key — `postgres` —
rather than `localhost` the way GitHub-hosted runners would.
Prisma migrate deploy was failing with P1001 (can't reach DB)
on this exact line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 16:40:09 -07:00
TehRiehlDeal c1d4113370 fix(ci): move workflow to .gitea/, install pnpm via corepack
CI / push (push) Has been skipped
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 12s
CI / vuln-scan (push) Successful in 15s
CI / test (push) Failing after 19s
CI / lint (push) Successful in 28s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
Two fixes for the runner:

1. Gitea Actions reads .gitea/workflows/, not .github/workflows/.
   Renamed the file (git mv) so it actually fires.

2. pnpm/action-setup@v4 was failing with `exitcode '1'` on this
   runner regardless of ordering vs setup-node. Switched to
   corepack (built into Node 16+) which downloads the pinned
   pnpm version on demand without needing a third-party action:

       corepack enable
       corepack prepare pnpm@9.12.0 --activate

   Same caching strategy as before (actions/cache@v4 keyed on
   the lockfile hash for the pnpm store directory).

Applied to both `test` and `lint` jobs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 16:36:27 -07:00
TehRiehlDeal 7f2e157cf6 fix(ci): install Node before pnpm, manual store cache
CI / sast (push) Successful in 11s
CI / secrets-scan (push) Successful in 5s
CI / lint (push) Failing after 33s
CI / test (push) Failing after 35s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / vuln-scan (push) Successful in 14s
CI / push (push) Has been skipped
Gitea runners (unlike GitHub-hosted ones) don't ship with Node
pre-installed, so pnpm/action-setup's self-installer fails with
"exitcode '1': failure" when it runs first — npm isn't on PATH
yet for it to do the global install.

Swap the ordering: actions/setup-node first, then pnpm-setup
with run_install: false, then actions/cache@v4 keyed on the
lockfile hash for the store directory. Same caching benefit as
setup-node's built-in cache: pnpm, just without the chicken-
and-egg dependency on pnpm existing first.

Applied to both `test` and `lint` jobs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 16:29:04 -07:00
TehRiehlDeal 5a2c6885e9 feat: runtime config + Dockerfiles + CI overhaul
CI / sast (push) Successful in 19s
CI / secrets-scan (push) Successful in 5s
CI / vuln-scan (push) Successful in 20s
CI / lint (push) Failing after 5s
CI / test (push) Failing after 7s
CI / image-scan (push) Has been skipped
CI / build-images (push) Has been skipped
CI / push (push) Has been skipped
Three coupled changes the deployable build pipeline needed:

1. Runtime config (window.__APP_CONFIG__)
   Vite bakes env vars at build time, which means one image per
   deploy target. Switched to a runtime-config pattern: the nginx
   container's entrypoint writes /config.js from env vars before
   serving, and index.html loads it before the React bundle. The
   client reads window.__APP_CONFIG__.apiBaseUrl via the new
   runtimeConfig.apiUrl() helper instead of hardcoding /api. One
   image, any backend.

2. Dockerfiles
   - packages/server/Dockerfile: multi-stage with `pnpm deploy
     --prod` for a flat node_modules. tini for PID 1, non-root
     user, alpine base. The `.prisma/client/` generated artifacts
     aren't tracked by `pnpm deploy` (not a declared dep) so we
     manually copy them from the builder's pnpm store into the
     same .pnpm@prisma+client+... directory that deploy preserved
     — without this, PrismaClient instantiation throws "did not
     initialize yet."
   - packages/client/Dockerfile: vite build + nginx:alpine
     runtime, custom nginx.conf with no-cache on /config.js and
     long-cache on hashed assets, /docker-entrypoint.d/40-app-
     config.sh materializes config.js at startup.
   - Added tsconfig.build.json on the server so `nest build` emits
     dist/main.js (the existing tsconfig included test/, which
     made tsc pick the package root as common prefix and produce
     dist/src/main.js).

3. CI overhaul (.github/workflows/ci.yml)
   Switched to the per-project pattern used elsewhere: parallel
   test / lint / secrets-scan / vuln-scan / sast jobs, then
   build-images, image-scan, and a gated push job. Push is
   main-only, checks Harbor for an existing version tag before
   pushing, signs images with cosign, and back-pushes per-package
   git tags (server-vX.Y.Z, client-vX.Y.Z). Renamed Harbor
   project to tehriehlincremental and the auth secrets to
   INCREMENTAL_USERNAME / INCREMENTAL_PASSWORD per request. Test
   job retains the Postgres service container so integration
   tests still run.

Verification:
- Server image: builds, boots, mounts all routes, "listening on
  :3001" — confirmed Prisma client initializes properly.
- Client image: builds, entrypoint generates /config.js with
  APP_API_BASE_URL=https://api.example.com/api confirmed via
  curl, index.html loads config.js before main bundle.
- pnpm -r typecheck / test / lint / format:check: all clean.
- 80 unit tests pass + 7 integration tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 16:23:51 -07:00
TehRiehlDeal a8210067d0 refactor(client): remove SVG tree; keep code backdrop as the canvas
CI / security (push) Failing after 3s
CI / test (push) Failing after 17s
CI / static (push) Failing after 18s
The SVG tree was redundant — every upgrade is already addressable
through the right-panel cards (with diff-gutter state, cost,
effect, and click-to-buy), and tree nodes weren't even wired for
interaction. Drops a lot of visual noise from the center pane
and lets the scrolling code be the actual editor view.

Removed:
- The <svg> + TreeEdge/TreeNode rendering inside LayerCanvas.
- The layoutLayer / isUpgradeUnlocked helpers (only the SVG
  consumed them).
- packages/client/src/components/game/TreeNode.tsx (now dead).
- The ownedUpgrades prop on LayerCanvas + its caller in Game.tsx.

Bumped CodeBackdrop visibility:
- main text rgba(148, 163, 184, 0.7), up from 0.45
- line numbers rgba(100, 116, 139, 0.7), up from 0.65 with a
  brighter base tone so they don't disappear next to the brighter
  main text

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 14:49:37 -07:00
TehRiehlDeal 2e872983d5 fix(client): give LayerCanvas a real height; tone CodeBackdrop subtle
CI / security (push) Failing after 3s
CI / test (push) Failing after 16s
CI / static (push) Failing after 19s
Root cause of the invisible code backdrop: the middle div in
Game.tsx wasn't a flex container, so LayerCanvas's `flex-1
min-h-0` had no effect and the canvas collapsed to content
height (zero — every child of LayerCanvas is absolutely
positioned). The CodeBackdrop sat inside a 0-height parent and
its absolute inset-0 box rendered at 0×width.

Why the tree was still visible at 0 height: SVG with viewBox
and preserveAspectRatio retains its drawn area even when the
host element's box-model height is 0, because most browsers
don't clip SVG content via the parent's overflow when the SVG
has explicit w-full h-full. The CodeBackdrop is a regular
block, so it followed the parent's 0 height and disappeared.

Fix: add `flex flex-col` to the middle div so LayerCanvas's
existing flex-1 correctly stretches to fill the row.

Also reverted the debug styling that was on the CodeBackdrop:
- removed the red dashed outline
- color back to slate-400 @ 45% (subtle, not bright cyan)
- font size back to 12px
- animation back to 90s
- line-number color toned to a darker slate

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 14:45:10 -07:00
TehRiehlDeal a3e1811c3a fix(client): make CodeBackdrop unmissable (debug); fix Dialog a11y
CI / security (push) Failing after 2s
CI / test (push) Failing after 16s
CI / static (push) Failing after 19s
CodeBackdrop visibility:
The previous version used Tailwind opacity-modifier classes
(text-slate-400/70) that might not be getting picked up by the
JIT scanner in some environments. Switched the critical text
styles to inline CSS so they're immune to Tailwind class-
detection issues. Also: a high-visibility red dashed outline on
the backdrop container as a debug aid — if the user can see the
outline, the component is mounting and we know any remaining
issue is a styling tweak. The bright cyan text + 60s scroll
cycle should make motion obvious. Both the outline and the
bright color will be tuned back down once we confirm visibility.

Dialog a11y warnings:
Radix wants DialogContent children to include a `DialogTitle`
primitive (for screen-reader announcement) and either a
`DialogDescription` or an explicit `aria-describedby={undefined}`
(to suppress the missing-description warning). Made DialogHeader
wrap its children in `DialogPrimitive.Title asChild` so the
visual styling stays in the wrapper but the a11y semantic is
correct. Added `aria-describedby={undefined}` as the default on
DialogContent — our modals don't have a meaningful one-sentence
description, and callers that want one can override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 14:37:45 -07:00
TehRiehlDeal b45f0d347c fix(client): make HMR explicit; brighten code backdrop
CI / security (push) Failing after 3s
CI / test (push) Failing after 17s
CI / static (push) Failing after 18s
Two unrelated fixes the user hit:

1) Vite HMR websocket failing in Firefox. Likely autodetect
   landing on the wrong host (dual-stack IPv4/IPv6 issue). Pin
   server.host to 127.0.0.1 and declare server.hmr explicitly
   (host=localhost, port=5173, protocol=ws, clientPort=5173).

2) CodeBackdrop was effectively invisible. text-fg-muted/40 on
   the deep bg-elev surface combined to about 0xff-equivalent
   alpha of ~10%, well below readable threshold. Bumped color
   token to text-slate-400/70 and font-size to 12px. Added a
   bottom-edge gradient so the scrolling code dims into the
   buyables strip rather than getting hard-cut.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 14:19:52 -07:00