- Drag-and-drop widget arrangement (main two-column zone + sidebar) with
per-widget span resize, persisted to Users.preferences.dashboard.layout
via saveDashboardLayout (migration included)
- New personal widgets: Wallet, Open Negotiations, My Tickets (View all),
Recent Notifications (per-row mark as read)
- Widgets that are empty or not visible to the viewer are omitted so they
never render as empty drag targets
- Registers both pending migrations and regenerates payload types/schema
- Missions.codeName is now optional (still unique when set); migration included
- Campaign and side-op dialogs accept an optional cover image (uploadUgcImage)
- Reminder toasts, evaluation aggregates, calendar, cards and mission headers
handle a null code name
- Mission read filter no longer hides drafts by status; visibility follows
ownershipAndStatus.visibility only
- briefing is now self-paced (player clicks Begin recall; no memo countdown)
- recall quiz has a hard server-validated time band per tier (45/60/75s limits, minimum plausible human time, 2.5s grace); scripted instant or over-limit submissions are rejected
- briefing is rendered on canvas: no selectable text in the DOM, copy/cut/context-menu/ctrl-a-c-x-s-p blocked; screenshots remain possible but burn quiz time
- ?seed=<n> URL param pins a deterministic scenario (no server meaning; used by the e2e spec to know the grid)
- e2e spec updated for the new flow and asserts no grid text leaks into the DOM
Deposits/withdrawals of stock (economy-mutating) are now superuser-only, while moving items around (transfer/place/collect) stays open to logistics-qualified users; seed roles updated to match. ManageStorageDialog reworked alongside its toolbar/button/view wrappers, and the structure page wires up the new permission split.
New /events page rendering the full game event ledger (system + narrative events) with type/target/source/date filtering, backed by a shared shadcn table component.
Adds a 'Mark as Ready' control on the mission detail page (owner or missions:update gated) plus the markSideOpReady server action. Status-gated to Concept/Planning/Scheduled; emits a mission:ready event. Stops the daily ready-reminders and lets missionEmbeds post the roll-call.
Side ops now credit their creator: mission cards get a 'Hosted by X' meta entry and the mission detail page gets a Host chip. Display name preferred over username; only shown for side ops with a populated createdBy.
Add provisioning a personal bank account, toggling background texture and callsign display, and copying the page link; the layout now passes user preferences and personal-account state so the palette can hide or handle them.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Normalize every submission to mono 96 kbps MP3 so takes play everywhere regardless of the recording codec; falls back to storing the original file when ffmpeg is unavailable or fails.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Submitters can attach an optional 300-char note to each take, and their own takes list now shows the description plus the reviewer feedback that was previously admin-only.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add /voiceover script list and take detail pages, teleprompter reader, voice capture/recorder components, submission server actions with a 9 MB audio cap, and a Contribute > Voiceover entry in the user menu.
Ultraworked with [Sisyphus](https://github.com/code-yeongju/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add getReferencePrices (modifier-applied NPC vendor price tip in CreateListingDialog) and resetEconomyState server action with a permission-gated 'Reset economy' button on listing cards for managers.
Ultraworked with [Sisyphus](https://github.com/code-yeongju/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Accept null content in updatePersonalExcerpt/updateIntelExcerpt and store
null when the submitted excerpt is empty instead of an empty Lexical state,
skipping the max-length check for blank submissions.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
WikiTemplates gains two fields: an optional css code field injected on
pages that use the template (selectors auto-scoped to a hash-based
.ptf-tpl-* class so templates never collide with each other or the app
shell), and a developer-only html field rendered verbatim (sanitized)
in place of the markdown body when transcluded.
- templateCss.ts scopes/sanitizes CSS, incl. nested at-rules; pages
and the editor live preview inject scoped styles and drop the Card
chrome in favor of a full-width article
- templateHtml.ts sanitizes HTML and splits prepared markdown around
expansion sentinels so raw HTML renders without rehype-raw
- templates.ts adds extractTemplateNames/extractPlaceholders/
extractDirectiveNames; prepareMarkdown threads an htmlTemplates map
- markdown pipeline gains rehype-highlight for fenced code blocks
- styles.css adds wiki prose typography + infobox demo styling
- docs: demo article and Infobox template authoring guide
Convert the dossier personal excerpt from a plain textarea to Lexical
rich text (matching the intel excerpt) with a 1000-character plain-text
limit, and replace the static read-only cards on the profile page with
inline DossierExcerptEditors: owners edit their personal excerpt,
profiles:intel_excerpt:update holders edit the intelligence record.
Changes are validated server-side (lexicalPlaintextLength) and emit
dossier:personal-excerpt-update / dossier:intel-excerpt-update events.