Round out the canvas editing experience.
- Dragging one card of a multi-selection now moves and persists the whole
selection (React Flow hands the full dragged set to onNodeDragStop).
- A category band is draggable by its header, which moves every technology in
that band; the band body stays click-through.
- A "Snap: On/Off" toolbar toggle (default on, remembered per browser) snaps
dragged cards to a 46px grid that divides the column and row steps, so
snapped cards line up with the automatic layout.
- Connection lines are adjustable: select an edge to reveal "+" handles at each
segment's midpoint (click to add a bend), drag bends to reroute, and
double-click a bend to remove it. Routes persist per browser.
- Edges route around technologies they are not connected to, and corners are
rounded, so lines no longer cut through unrelated cards.
- Ctrl/Cmd-clicking to multi-select no longer pops the side panel over the
canvas.
Move the record contract, planning, and upsert logic out of the CLI script
and into src/lib/arma-import/catalog.ts so it can be unit-tested and reused.
The CLI (importArmaLibrary.ts) becomes a thin wrapper. Records are upserted
into assets (keyed by className) and vehicles (by classname); thumbnails are
materialized from .paa inventory icons, and fuel config is resolved per
record type. Adds integration coverage for the catalog import path.
Route the game's `money.delta` events into the web banking system so a
player's personal bank account tracks their in-game wallet.
- New `processMoneyEvent` (src/lib/arma-bridge/money.ts) applies a parsed
money delta to the player's personal account: debits go to the unit
treasury (or leave if no treasury), refunds hit the treasury first and
fall back to a plain deposit, and rewards/grants deposit directly.
- Wire the handler into ArmaSyncEvents and add a `POST /api/arma/v1/balances`
route (get_balances) that returns personal balances for Steam IDs, omitting
unlinked IDs so the game keeps its own stored value.
- Events dedupe by serverId+eventId so a retried delivery never
double-charges, and the whole path never throws.
- `tech-tree-layout.int.spec.ts`: pure layout (wrapping, manual overrides,
category bands, forward-edge warnings, cycles, catalog-stability).
- `tech-tree-prereq-edits.int.spec.ts`: add/remove/switch prerequisites with
cycle rejection and the pin-on-connect stability contract.
- `tech-tree-stability.int.spec.ts`: freeze pins every relocated card on a
data change, plus edge visuals (per-path colors, per-source lanes).
- `tech-categories.int.spec.ts`: CRUD, RBAC, division gating, FK set-null,
position round-trip.
- `tech-tree.e2e.spec.ts`: nav entry, render, preview/panel edits, both
quick-create paths, drag persistence + Auto-arrange, save positions
surviving a reload, connect/disconnect, and multi-column wrapping.
Covers the category registry sync, create and update gating per role (plain user, admin, developer, guest), the update-time escalation block, and read gating.
The suite could not pass on the test database: global setup now seeds the shared dev/Test123 login with a superuser Roles doc and the dashboard rank, and points its own Payload connection at the test database. Playwright runs one worker with generous test and expect timeouts because first navigation to a route triggers a cold webpack compile, and gains E2E_BROWSER_PATH to swap the browser binary when the installed Vivaldi crashes under automation. The landing page gains the missing h1 the guest specs assert, login flows wait for the redirect to land, strict-mode locator collisions get exact or level-scoped matches, the session banner test re-signs the login claims with a short expiry using the runtime secret, and the reservation dialog click forces the dispatch and verifies the dialog opened (the trigger goes aria-hidden behind the modal mid-click, which stranded the retry loop).
Pages declare brief tours in code, keyed to stable data-tour anchors. The layout resolves each visitor's access signals (intelligence and logistics qualifications, admin-panel access, feature flags) and step filtering hides what a user cannot reach, mirroring the sidebar. A tour auto-starts the first time a browser visits its page; dismissals are remembered per browser in localStorage. The header gains a replay control for on-demand runs on toured routes, the tour card gains Skip all tours on this device to opt a browser out of auto-starts, and the Account page holds the re-enable toggle. The card and spotlight get a distinct overlay treatment (glow, accent strip, outlined target).
Players meeting a role's minimum rank and MOS can queue for mission roles before op time. Slot holders are recomputed statelessly from signup order plus current RSVP (first N committed signups hold the role); attendance writes diff holders around the write so RSVP changes auto-promote, with notifications and events. Adds mission-role-signups collection, roleSignups/missionTime libs, sign-up panel, role slots + rich-text descriptions (with RoleDescription renderer), orphan-pruning hook, migrations, tests, and AGENTS.md docs.
Replace the maps:create/update/delete catch-all for map features: roads, zones, and nodes each take a per-collection author permission, structure placement takes maps:place (still co-gated with the logistics qualification), and GeoJSON import requires all three author permissions. The map view page keys edit UI per feature type; admin role seed grants the new values. Tests updated for the permission split.
Touch drags flip pages: the page follows the finger live, the axis locks
on first movement so vertical pans never flip, and edge drags get
rubber-band resistance. Swipes starting on interactive controls are
ignored. Swipe intent lives in bookSwipeDirection with unit tests.
- Introduced voiceover relationship to StoryBeats with AI-generated flag.
- Added BeatVoiceover component for playing audio narrations with a minimal interface.
- Updated StoryPage, StoryViewer, and StoryTableOfContents to display and handle voiceovers.
- Created Voiceovers collection for uploading and managing audio narrations for beats.
- Included migration script to support voiceover functionality in the database.
- getPendingSurveys resolves open + eligible + unsubmitted surveys (single eligibility path shared with the list page)
- GET /api/surveys/pending poll endpoint (cookie auth)
- SurveyReminderToasts in the frontend layout: polls every 5 min, toasts at most once per 15 min, action deep-links into the modal
- integration coverage for the pending resolution
- forms collection: targeting group (all/users/roles/qualifications), activeFrom/activeUntil window, radio/date field blocks enabled
- form-submissions: server-side user attribution, expiry and audience gates, one attempt per user
- forms/submissions permissions in the Surveys registry group + admin role seed grants
- frontend /surveys list and /surveys/[id] take page with client block renderer
- register the pending role permission enum values migration
- announcements collection: info/success/warning/error variants, banner/modal/dialog/toast display types, up to 3 action buttons, dismissible flag, activeFrom/activeUntil windows, audience targeting (all/users/roles) and path include/exclude matching copied from the Shims pattern
- pure client-safe evaluation lib (enabled, time window, audience via roleDoc IDs, path match) plus per-browser localStorage dismissals
- AnnouncementHost mounted in the (frontend) layout: banners render inline, toasts via sonner custom markup, modals and confirmation dialogs queue one at a time
- permissions announcements:create/read/update/delete + admin:announcements:manage
- description is Lexical richText rendered via RichText/lexical-content on all surfaces; role targeting uses the dynamic Roles collection
- hand-written migrations with snapshot sidecars (collection, roleDocs targeting, description richtext conversion) + integration tests (12 passing)
Bootstrap a per-run test database (ensure, push schema, truncate, seed baselines, repair sequences), wire vitest/playwright globals, and gate schema push behind DB_PUSH. Serial test files share the one database.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- scopedAdminPageAccess replaces requireAdminPageAccess: technologies pass for
intelligence division members, assets/resources/vehicles for logistics;
everyone else still needs admin:<slug>:manage
- technology approval is stripped from create/update below admin so division
members can never self-approve; technologies access moves to the
intelligence permission
- assets/resources/vehicles move to logistics division permissions
- vehicle fixtures are named to sort first so the 50-item name-sorted options
window includes them even on a DB crowded with older idle vehicles
- the respawn bridge assertion filters to this run's steam ids so leaked rows
from aborted runs cannot break exact equality
Satisfies the Next 16 rule that page files only export page fields:
getSupplyBoxDetail/getSupplyBoxList and getReservationList/getReservationDetail
move to reservations/surface.ts and pages import from it.
- Maps gain worldMap flag, description, freeform weather, latitude/longitude,
easeOfAccess and factionControl; picker cards render them as stat tags
- resource-nodes gain a shared icon set and may exist with no resources as
plain marker nodes (placement ignores marker-only nodes)
- sidebar gains the World Map entry; Infrastructure section gets defaultOpen
- migrations 20260921_120000/123000/130500/134000; includes the generated
types/schema sweep (currency emblem fields land in the next commit)
Add the create-reservation dialog and its option sources for todo 3 of
ops-reservations-and-crate-sync.
- src/lib/operations/reservationOptions.ts: server-only option pool
(listReservationOptions) returning upcoming unit-visible missions, idle
unreserved vehicles, operational structures, budget accounts (no
balances), per-origin stock, and a capped roster.
- src/components/frontend/operations/reservations/CreateReservationDialog.tsx:
client dialog with core fields, personnel/vehicle multi-picks, cargo
line repeater, budget + expiry; client pre-validation mirrors the
server validator; engine errors render verbatim.
- Mount the dialog on the reservations list page, server-gated by
operation-reservations:create + hasLogisticsQualification.
- Integration tests for the option scopes; e2e spec written (run in F3).
tsc clean; 5/5 integration tests pass.
Deterministic, explainable, advisory readiness aggregates per subject behind a permission gate, and mission-scoped zone pressure with read-time decay yielding route-risk and yield multipliers.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Link operation loot to the supply/demand ledger with faucet/drain caps and provenance-marked notes; repair buyListing to preflight locker credit before payment with a compensating refund on credit failure.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Reserve personnel, vehicles, cargo, and budget with idempotent keys and exactly-once settlement; extraction effects preflight before any write so validated HQ deposits and locker credits are final and survive mission failure. Failure sweeps touch only unextracted reservations.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Consume raw arma-sync-events into a validated, idempotent operation ledger: contract v1, reject/dead-letter with zero effects, effect keys with unique-constraint race handling, and a staged-activation flag hook. Registers the operations permission domains.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Notice-day gating in Eastern time across DST, upcoming-Friday range,
stable dedup token, slot-open semantics (drafts hold the slot,
cancelled do not, other days do not count), and claimant resolution
(superuser exclusion, earliest-created wins, cancel reverts the
claim).
- map-zones type gains territory and country border (informational political
boundaries that never gate placement, default fills amber 0.1 / violet 0.06)
- map-roads labelVisible is tri-state: unset means auto, where paved roads
always label and dirt/trail only label at 4 m/px or finer (roadLabelShown)
- migration clears the old implicit label default on non-paved roads
- new pure snapping lib: findSnap (vertices own their neighborhood, edges must
be twice as close), closestPointOnSegment, segmentMidpoints
- tests for label angles, tri-state rule, snapping, political zones
Add pure routing, placement, point, and style libs plus map-system tests. Shipments gain a map relationship and routePath snapshot; ground vehicles route over the road network via Dijkstra while air and sea travel straight-line. Vehicle types declare a ground/air/sea transportMode and transit math works in meters.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add maps basemap config (world size, image/tiles mode, zoom defaults, isActive), map-roads, map-zones, and resource-nodes collections with maps:* permissions. Track game-structures map placement, meter coordinates, and label visibility/priority. Bump proximityThreshold to meters and adapt side-op map creation plus test fixtures to the new required fields.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
CardPicker propagated null into onChange when no card was selected yet,
so the board picker's parent coerced every first pick to 2 of spades and
the hole-card pickers could never hold a value, keeping Queue hand
disabled forever. Picks now build a complete card, preserving any
already-chosen half, with regression tests on the extracted helper.
Co-authored-by: Sisyphus <sisyphus@ohmyopencode.dev>
Chip service and actions, fixed ChipBankCard with delta popup, sounds, spinner, and arena integration; chip bank migration and tests. Arena diffs are the chip-card mounts and join/leave spinner dispatches.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Engine, felt, controls, sounds, card skins, and dev popups; SSE events in the realtime hook, nav and command palette entries, stats migration, and the six blackjack test suites.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Table components, popup system, dev panel, and the actions/page wiring, with popup, table, and dev-control tests.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Poker lib layer: engine, tables, stats, hand setup, shared table names, with engine tests.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Promotion service lib, ceiling field on Ranks, permission gate, migration, and GM promote button with tests.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Every made hand maps to a presentation tier from high card (no styling) through brass, silver, and gold pairs to a sapphire straight flush and a royal flush prismatic treatment. The live hand hint carries the tier, so the you-have placard glows with your hand's rank, and royals now read as royal flush.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>