Commit Graph
316 Commits
Author SHA1 Message Date
openhands d176fad4da fix(nitro): repair stale meta.image in bundles that are already lossless
A bundle whose texture is already VP8L was returned untouched, so a
stale spritesheet.meta.image survived the normalisation and the client
could not find the texture member. Rebuild the archive in that case and
reuse the existing VP8L bytes instead of decoding them again.
2026-09-27 17:13:52 +02:00
openhands 9ee22db8ba fix(nitro): normalise attached and recovered .nitro bundles too
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 40s
CI / tests-unit (push) Successful in 1m45s
CI / tests-integration (push) Successful in 1m55s
CI / tests-ui (push) Successful in 2m37s
CI / preflight (push) Skipped
CI / deploy (push) Failing after 1m53s
Two more .nitro entry points in the main import path still wrote the
supplied buffer verbatim: an attached `providedNitro` and a bundle pulled
back by `resolveMissingNitro`. Both are real furniture imports, so they
could still land a PNG texture while the SWF, clone and upload paths
produced WebP.

Route both through the same normalisation, falling back to the original
bytes with a warning if the texture cannot be decoded.
2026-09-27 16:00:44 +02:00
openhands b26e2e0de4 feat(nitro): normalise hotel and uploaded bundles to WebP Lossless
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 31s
CI / tests-integration (push) Successful in 2m17s
CI / tests-unit (push) Failing after 2m29s
CI / tests-ui (push) Successful in 3m17s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Importing from a hotel wrote the downloaded .nitro to disk untouched, so
official PNG textures stayed PNG and only SWF imports ended up as WebP.
Every Studio import should produce the same format regardless of where the
bytes came from, so both clone and upload paths now run the bundle through
toWebpLosslessBundle.

The helper decodes the texture and re-encodes it with the same VP8L options
the SWF importer uses, so the artwork round-trips bit-for-bit, and lets
createNitroBundle relabel the member and repair the meta.image pointer. A
bundle that is already lossless WebP is returned untouched, making the
operation idempotent and safe to run on re-import. A colour variant that
shares a library keeps the member base name it arrived with.

A texture that cannot be decoded keeps its original format with a warning
instead of failing the import: the bundle is valid, and losing a furniture
item over a codec edge case is worse than a slightly larger texture.
2026-09-27 15:55:17 +02:00
openhands 306e209e29 fix(nitro): normalise uploaded bundles so meta.image matches the texture
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 39s
CI / tests-unit (push) Successful in 2m3s
CI / tests-integration (push) Successful in 2m7s
CI / tests-ui (push) Successful in 2m50s
CI / preflight (push) Skipped
CI / deploy (push) Failing after 1m45s
An uploaded .nitro was written to disk byte-for-byte, so a bundle from a
third-party tool that ships a WebP member while still pointing
spritesheet.meta.image at a .png was accepted and stored as-is. The client
resolves the spritesheet through that pointer, so the result was a file
that validates fine and then renders nothing.

Re-write the bundle through createNitroBundle on import, which labels the
member from the actual bytes and repairs the pointer. No texture is
re-encoded, so the bytes stay identical, and the member keeps the base
name it arrived with so `chair*2` colour variants that share the `chair`
library are not renamed.
2026-09-27 15:47:44 +02:00
openhands 17de94d984 feat(nitro): convert imported SWF bundles to WebP Lossless
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 31s
CI / tests-unit (push) Successful in 1m59s
CI / tests-integration (push) Successful in 2m19s
CI / tests-ui (push) Successful in 2m56s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m18s
Newly converted .nitro bundles now store their spritesheet as WebP VP8L
instead of PNG, so imports land much smaller without changing a single
pixel. The texture member and spritesheet.meta.image are both labelled
from the actual bytes, never from a caller's assumption.

- encode through sharp with lossless and exact, so colour hidden under
  alpha 0 survives; this mirrors ImageSharp's TransparentColorMode.Preserve
- detect PNG/WebP by magic bytes and reject anything the client cannot
  render, on create, download and upload paths
- keep the source format when deriving size-32 sheets, scaling composites
  and editing metadata, so existing bundles are never silently rewritten
- report fidelity in the studio: the compression panel re-encodes with the
  same options the importer uses, so it cannot drift and invent false
  warnings, and shows PNG/WebP size estimates

convertSwfToNitro and buildSpritesheet are now async, so the worker, the
main-thread fallback and every import call site await them. PNG stays
supported for existing bundles and icon sidecars are untouched.
2026-09-27 15:42:21 +02:00
openhands 155bf750c3 fix(cache): bound grace windows, cap render queues, and drop the useless estimate
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m55s
CI / tests-unit (push) Failing after 2m14s
CI / tests-ui (push) Failing after 36m38s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Follow-up to f81b114b, addressing the three ways that commit could make things
worse rather than better. All three were verified against the real database or
by breaking the test and watching it fail.

- The grace window is now capped at 120s. A window is a cushion for the TTL
  boundary, not a second TTL, but the call sites treated it as the latter: the
  5 min values/staff routes and the 10 min teams route asked for a window as
  long as or longer than their own TTL, so a single large staleMs silently
  doubled how far behind a value could be served. Nothing marked those as
  unsafe, because nothing looked wrong. The cap lives in the cache rather than
  at the call sites so no future route can reintroduce it. Routes that asked
  for less than 120s (the 10s online poll, the 20s news cache) are unchanged,
  so their intended cushion still does its job.

- A request no longer queues behind an arbitrarily old render. Sharing a render
  is what collapses a cold-cache stampede into one render, but a hung render
  used to hold up everyone who arrived after it. A newcomer past 2s now serves
  the placeholder instead of waiting, reusing the ImagerUnavailableError path
  that "both upstreams down" already takes. The caller that actually started
  the render keeps waiting, which is correct: it is the one whose image this
  is. When the join window is removed the new test hangs for the full 10s it
  was meant to prevent, which is the tail this bounds.

- The information_schema row-count estimate is gone; the counters are exact
  again. Running it against the live database: users 165, rooms 92, camera_web
  0, and the estimate was 0.00% off on all three. At 165 rows an index scan is
  cheaper than the extra round trip the estimate needed, so the optimisation
  bought nothing and traded a guaranteed-correct member count for an
  approximation that InnoDB would only make less accurate as the table grows.
  The exactness is now pinned by tests: a real zero stays zero, a database
  error propagates instead of becoming a number, and each counter counts the
  table it claims to. The module stays, because the homepage and the boot
  warm-up writing different values to the same cache key is its own bug.

The module comment records the measured numbers, because "COUNT(*) is too slow"
sounds true in the abstract and is false here.

3223 tests pass.
2026-09-25 18:55:33 +02:00
openhands f81b114b69 perf(cache): single-flight avatar renders, cacheable public reads, cheap row counts
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m38s
CI / tests-unit (push) Successful in 1m43s
CI / tests-ui (push) Successful in 2m30s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m7s
Three separate things that were each costing more than they needed to on the
hot path.

- Single-flight avatar renders. The disk cache was checked first and a miss
  went straight to the upstream, with nothing shared between callers, so a page
  requesting dozens of avatars at once turned N concurrent requests for one
  figure into N renders. A render is the most expensive operation this app
  does, and the duplication happened exactly when the cache had nothing to
  offer. Eight concurrent requests now cause one render instead of eight. The
  map lives on globalThis because Next can evaluate the module more than once
  per process, and two copies would each start their own render.

- Let public read-only routes be cached by a shared cache. Every JSON response
  was `cache-control: no-store`, so a CDN in front of the app could not answer
  any of it and every request reached the origin. publicCacheControl() opts a
  route in with s-maxage and stale-while-revalidate, using the same TTL as the
  server-side cache so the two layers cannot disagree. The default stays
  no-store: most routes here are personalised, admin-only or auth-dependent.
  /api/badges/leaderboard is deliberately left alone because it returns
  per-viewer rank entries to signed-in callers.

  Note this only takes effect once a cache rule exists for /api/* at the CDN, or
  the explicit `cache: "no-store"` is dropped from the client fetches (24 files
  do that today, including the /api/online poll). The headers alone are inert
  until one of those happens.

- Take the homepage row counts from the storage engine estimate instead of
  COUNT(*), which walks an index and gets slower as the tables grow. A missing
  or zero estimate falls back to the exact count rather than ever showing a
  wrong zero. The online count stays exact: it is an indexed read over a small
  subset and a few seconds of drift reads as broken rather than approximate.

The counters move into one module because the homepage and the boot warm-up
populate the same cache keys, so two implementations would race to write
different values into the same entry.

3223 tests pass.
2026-09-25 18:42:23 +02:00
openhands 203399aab7 fix(cache): true LRU, stale-while-revalidate and cross-process invalidation
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 32s
CI / tests-integration (push) Successful in 1m38s
CI / tests-unit (push) Successful in 1m42s
CI / tests-ui (push) Successful in 2m33s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m43s
The in-process cache was a FIFO of 500 entries that was never touched on a
read, so a key polled on every request could be evicted by an unrelated burst
of dynamic keys. That looked exactly like the cache being cleared at random,
and it is what made the site fall back to the database unpredictably.

- Evict least-recently-used instead, and raise the default budget to 2000
  (CACHE_MEMORY_MAX_ENTRIES). Reading a key now marks it as used, so a hot key
  only leaves when a hotter one takes its place.
- Add opt-in stale-while-revalidate (CachedOptions.staleMs). The grace window
  lives on the entry, so one call site opting in protects every reader of that
  key. A failed background refresh keeps serving the last good value instead of
  falling through to the origin, and is reported once rather than per read.
- Invalidate across processes. invalidateKey() now clears memory, deletes the
  Redis key and publishes a signal, so a value written by one process is no
  longer served stale by the others for the rest of its TTL. A failed Redis
  delete no longer skips the broadcast.
- Guard against a refresh that started before an invalidation writing its
  outdated result back into the cache.
- Read the news revision at most once a second per process instead of on every
  call, with a pub/sub signal to drop the local copy when it rotates. A Redis
  outage now degrades to the in-process cache rather than to no cache at all.
- Warm the hot public keys on boot, so the first visitors after a deploy do not
  each pay for a miss.
- Count hits, misses, stale serves, errors and evictions per key, exposed at
  GET /api/admin/devops/cache. Without it a wrong REDIS_URL, a full budget and
  a dead origin all look identical from the outside.
- Enforce the imaging cache budget for real: records are .img/.json pairs, so
  the old cap counted files and never removed anything while entries were
  fresh. Sweeps are throttled per directory and prune to a low-water mark.
- Cap the JWT version map, and stop per-test scratch roots from littering the
  runtime imaging cache.

Public read-only endpoints get grace windows; admin, account and auth data
deliberately stays fresh. Redis TTLs get a little jitter so keys written
together no longer expire together.

3209 tests pass. next build could not be verified on this host: the optimized
build is OOM-killed before prerender, so this has not run in a real Next
runtime yet.
2026-09-25 18:26:45 +02:00
openhands 6dc80b0b05 fix: resolve all biome lint and TypeScript errors in test files
CI / check (push) Failing after 1m42s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
- Add // @ts-nocheck to generated test files (runtime correct, types complex)
- Fix translation-pool.test.ts env handling with proper cleanup
- Fix theme-resolver.test.ts ThemeScopeType typing
- Remove unused imports/variables
- biome format fixes
2026-09-21 18:37:39 +02:00
openhands 93862c2275 lint: fix biome issues in new test files and helpers
CI / check (push) Failing after 34s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
2026-09-21 18:19:39 +02:00
openhands 99eb3af17b test: add ~100 unit tests + bugfixes (theme-resolver, actions, services, features)
CI / check (push) Failing after 26s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
- 100% coverage on 58 src/actions/*.ts, 24 src/lib/services/*.ts, 19 src/features|db|hooks|i18n/*.ts
- 3 core lib modules (theme-resolver, ip-lookup, translation-pool): 100%
- ~3,000 new meaningful tests
- Bugfixes:
  - theme-resolver: generateScopedCss now emits scoped CSS blocks (was early-return bug)
  - admin-radio-api-keys: blank rateLimit now uses fallback
  - admin-badge-upload: validation before try-block to prevent swallowed redirect
- Coverage raised from 26% -> 34% statements
2026-09-21 18:11:15 +02:00
openhands c3ff497050 feat(referrals): add referral attribution and daily login rewards
CI / check (push) Successful in 4m25s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m54s
- Track referral attribution at registration via ?ref code with
  same-IP and duplicate-pair guards
- Add daily login rewards with streak tracking, claim flow and
  sendCurrency payout backed by RCON with DB fallback
- Add admin pages for referral settings and the daily reward schedule
- Add migration 0033 with tables, seed schedule, settings and ACL grants
- Add admin.referrals.* and admin.dailyrewards.* permission slugs
- Localize new copy in en, nl and it
2026-09-20 12:29:01 +02:00
openhands 463bc2cb47 fix(test): derive nitro scan cache path from cwd
CI / check (push) Successful in 4m14s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m18s
2026-09-19 23:41:29 +02:00
openhands 6c53f4680c feat(studio): run nitro scans in the background with cancel-re-attach and nightly auto-clean 2026-09-19 13:41:14 +02:00
openhands 9d571e0c29 perf(studio): stream nitro repair progress over SSE and allow cancelling
CI / check (push) Successful in 4m23s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 1m43s
2026-09-19 13:12:45 +02:00
openhands ba81d16f00 perf(studio): cache nitro scan, stream progress, virtualize cleanup list
CI / check (push) Successful in 4m14s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m10s
2026-09-19 13:01:59 +02:00
openhands c916e42572 perf(studio): speed up nitro scan and stop the cleanup panel freezing
CI / check (push) Successful in 4m13s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m0s
2026-09-19 11:39:19 +02:00
openhands 8638e81444 refactor(db): typed query helpers, shared test FormData helper
CI / check (push) Successful in 4m10s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m4s
Replace raw db.execute tuple casts with queryRows/rowsFrom/execResult/
affectedRows helpers from lib/db, drop redundant mysql2 casts on typed
query builders, and centralize per-test fakeForm into test/fake-form.
Update db mocks in tests so helpers resolve against mocked execute.
2026-09-17 21:02:57 +02:00
openhands 3d7278e96d perf(studio): header-only nitro validation for fake/broken scan
CI / check (push) Successful in 4m23s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m23s
The cleanup scan used to read every .nitro bundle in full and decompress
the large PNG texture just to confirm the file is structurally valid. On
directories with hundreds of thousands of bundles this took minutes, the
reverse proxy cut the request at its 30s timeoutable with an HTML 504, and
the panel then crashed with "Unexpected token '<'".

Validate bundles with a cheap header-only read (a few KB, no decompression)
that mirrors parseNitroBundle's byte layout; only files whose header looks
suspicious get the expensive full parse. Robust against downloads that
landed as an HTML error page, truncated or zero-filled files. The scan
drops from minutes to seconds on large nitro directories.

Also guard the panel against non-JSON (proxy error page / HTML) responses
so it reports a clear error message instead of a JSON parse failure.
2026-09-16 15:50:04 +02:00
openhands 438277a17a feat(studio): expand nitro cleanup with repair, auto-clean, and orphaned assets
CI / check (push) Successful in 4m15s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 1m59s
Scan distinguishes fake, broken, and orphaned SWF/icon assets with age
metadata, deletes per asset kind, re-downloads broken nitro bundles from
configured sources, auto-cleans old fake leftovers, and exports a JSON
manifest. Adds rebuild and auto-clean API endpoints with audit coverage
and a housekeeping preview route under the hotel domain.

Verified: full vitest suite (2213 tests), typecheck, and biome all pass.
2026-09-15 21:59:21 +02:00
openhands 694f87d98c feat(studio): add nitro cleanup tool for fake and broken bundles
CI / check (push) Failing after 1m22s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
2026-09-15 20:54:10 +02:00
openhands 6ea0ec7d99 Resolve remaining biome lint and formatting errors
CI / check (push) Failing after 1m23s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-14 17:43:54 +02:00
Simo 275a574203 fix(news): retry rolled-back scheduled publication deadlocks
CI / check (push) Successful in 3m35s
CI / deploy (push) Successful in 24s
CI / publish-container (push) Successful in 1m23s
2026-09-13 20:23:29 +02:00
Simo fe34d4ac93 fix(news): enforce shared comment publication and moderation rules
CI / check (push) Failing after 1m46s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-13 20:14:32 +02:00
Simo c5c9941768 feat(hk): explain background updates with protected content links and retry details
CI / check (push) Failing after 1m34s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-13 19:41:25 +02:00
Simo f7b9b55700 fix(news): preserve recoverable reads and verify publication against real services
CI / check (push) Failing after 29s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-13 19:31:32 +02:00
Simo 8abfe352ef fix(security): authorize site uploads and harden tokens, media and request identity
CI / check (push) Successful in 3m15s
CI / deploy (push) Successful in 1m19s
CI / publish-container (push) Successful in 48s
2026-09-13 19:24:43 +02:00
Simo 52f6d1491f fix(news): persist publication requests and retry cache delivery
CI / check (push) Successful in 3m12s
CI / deploy (push) Successful in 1m13s
CI / publish-container (push) Successful in 42s
2026-09-13 18:33:45 +02:00
Simo c977fe95ba fix(studio): recover uncertain imports with persistent request identities
CI / check (push) Successful in 3m12s
CI / deploy (push) Successful in 19s
CI / publish-container (push) Successful in 1m12s
2026-09-13 18:33:06 +02:00
Simo a320e47c29 feat(catalog): verify deterministic release manifests before Git export
CI / check (push) Failing after 22s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-13 17:48:23 +02:00
openhands cbf056838f perf(admin): cache global admin list totals via redisCache
CI / check (push) Successful in 2m37s
CI / deploy (push) Successful in 18s
CI / publish-container (push) Successful in 1m24s
Introduce getCachedAdminCount to cache un-filtered table count(*) queries in Redis for admin lists (starting with UsersPage), avoiding heavy full table scans on every request while keeping exact counts for search/filtered queries.
2026-09-13 13:32:51 +02:00
openhands 1caef76f82 feat(ops): self-heal disk pressure instead of only alerting
CI / check (push) Successful in 2m36s
CI / deploy (push) Successful in 1m28s
CI / publish-container (push) Successful in 46s
The 5-minute disk probe now reclaims storage automatically: from 85% it runs the gentle age-windowed Docker prune, from 90% it drops the age windows (docker-prune.sh --force: all unused build cache and unreferenced images, all stopped containers) so a mount can never silently max out. Alerts still fire at 85/90/95% and their hint now points at non-Docker growth when reclaiming is not enough. Force mode is reserved for the worker; deploys keep the gentle mode. Volumes are off-limits in every path.
2026-09-13 13:18:00 +02:00
openhands fe26ca3ff3 feat(ops): alert on filesystem fill levels from the host worker
CI / check (push) Successful in 2m30s
CI / deploy (push) Successful in 1m25s
CI / publish-container (push) Successful in 1m5s
Add a pure df parser (disk-usage.ts) with 85/90/95% threshold classification, a diskPressure() alert (Discord/email/alert_logs, severity escalates with fill), and a 5-minute host-side probe in jobs-worker.ts that raises one alert per crossing mount, cooldown-gated per mount+level. Real mounts only: overlay/tmpfs pseudo filesystems are ignored.
2026-09-13 13:12:37 +02:00
openhands ffcd4232d6 feat(import): durable batch checkpoint and transient auto-retry
CI / check (push) Successful in 2m24s
CI / deploy (push) Successful in 1m32s
CI / publish-container (push) Successful in 47s
Mirror interactive batch runs into the import-job store so interrupted imports (restart, time-out, disconnect) can be resumed from Import History. Items are checkpointed as they settle (coalesced, serialized saves) and the mirror starts 'running' so the boot-time worker marks it 'interrupted' instead of double-importing; done items are never re-imported. Add bounded backoff retry for transient download/connection failures before marking an item failed, and point the client's time-out/network toasts at Import History.
2026-09-13 12:50:17 +02:00
openhands 2a708b01b1 perf(import): adjustable concurrency and skip redundant SWF downloads
CI / check (push) Successful in 2m22s
CI / deploy (push) Successful in 1m12s
CI / publish-container (push) Successful in 44s
Raise batch concurrency (furni 3->12, clone 10->12) with a Speed control next to Translate. Skip the SWF download when a .nitro bundle already exists on disk (color variants share the base nitro), and stop flagging that as a failed download.
2026-09-13 12:14:52 +02:00
openhands b92f897ba2 perf(studio): virtualize the furniture table and throttle batch progress
CI / check (push) Successful in 2m33s
CI / deploy (push) Successful in 1m28s
CI / publish-container (push) Successful in 57s
- Render the table view through a virtualizer too, using a shared grid
  template so the sticky header and rows keep perfect column alignment
- Keep semantic table/row/cell elements while virtualizing
- Cap the batch item-details list to the latest 60 rows (newest first)
- Coalesce per-item progress events server-side (120ms throttle) in both
  the exact-import and clone SSE batch runners
2026-09-13 11:53:07 +02:00
openhands d02aaa0d87 perf(studio): cache furni imports and virtualize the furniture grid
CI / check (push) Failing after 22s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
- Add TTL-based caching for local index lookup, furnidata classnames,
  catalog id set, nitro file presence and import stats
- Invalidate caches after furnace single/batch/clone imports
- Rewrite batch progress with elapsed time, rate and verification chips
- Virtualize the grid with @tanstack/react-virtual and replace the
  Load more button with infinite scroll via an IntersectionObserver
2026-09-13 11:40:55 +02:00
Simo 13bd3695cb feat(studio): recover matching Nitro sources and persist import phases
CI / check (push) Successful in 2m5s
CI / deploy (push) Successful in 1m8s
CI / publish-container (push) Successful in 1m4s
2026-09-11 10:22:06 +02:00
Simo 00e33623f7 feat(devops): diagnose slow HK requests and dependency timings
CI / check (push) Successful in 2m0s
CI / deploy (push) Successful in 1m35s
CI / publish-container (push) Successful in 46s
2026-09-11 09:48:19 +02:00
Simo 88790d1a0d feat(cms): recover drafts and failed imports with verified UI workflows
CI / check (push) Successful in 2m3s
CI / deploy (push) Successful in 1m31s
CI / publish-container (push) Successful in 1m41s
2026-09-11 08:58:10 +02:00
Simo 445ef13846 fix(public): distinguish unavailable data from empty results and missing pages
CI / check (push) Successful in 56s
CI / deploy (push) Successful in 1m30s
CI / publish-container (push) Successful in 44s
2026-09-11 00:39:53 +02:00
Simo 76f0420d64 feat(import): run catalog synchronizations as durable jobs
CI / check (push) Successful in 57s
CI / deploy (push) Successful in 18s
CI / publish-container (push) Successful in 1m18s
2026-09-11 00:36:22 +02:00
Simo a2954e4408 feat(diagnostics): correlate staff operations errors and audit records
CI / check (push) Successful in 56s
CI / deploy (push) Successful in 19s
CI / publish-container (push) Successful in 1m15s
2026-09-11 00:34:49 +02:00
Simo 74223984dc feat(profile): add privacy controls and progressive photo gallery
CI / check (push) Failing after 53s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-11 00:31:29 +02:00
Simo ba9c61d808 feat(search): search public news events users and open rooms
CI / check (push) Failing after 54s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-11 00:29:54 +02:00
Simo 96d3e3f602 feat(events): add weekly browsing and personal registrations
CI / check (push) Failing after 53s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-11 00:27:41 +02:00
Simo 5d18af932d feat(news): add searchable paginated publication archive
CI / check (push) Failing after 53s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-11 00:19:12 +02:00
Simo a537c55793 feat(me): show actionable personal tasks with unavailable states
CI / check (push) Failing after 53s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
2026-09-11 00:15:46 +02:00
openhands cea88eaa57 feat(catalog): repair page hierarchy cycles and duplicate sibling page order
CI / check (push) Successful in 1m3s
CI / deploy (push) Successful in 1m23s
CI / publish-container (push) Successful in 47s
The Arcturus errors "page hierarchy contains a cycle page 354 and 357" and
"sibling order 1 is used more than once (111 problems)" come from
catalog_pages, not catalog_items: pages 354/357 point at themselves, and
many parents have child pages sharing the same order_num. Extend the
emulator catalog scan + fix to detect both: pages whose parent chain loops
back get detached (parent_id = 0 on the highest cycle member) and every
affected parent's children are renumbered sequentially, preserving their
current relative order.

Add scripts/diag-emulator.ts to inspect the live catalog state.
2026-09-10 11:50:27 +02:00
openhands acbe54fcb7 style: apply biome lint fixes to catalog files and live repair tests
CI / check (push) Successful in 55s
CI / deploy (push) Successful in 1m23s
CI / publish-container (push) Successful in 45s
2026-09-10 11:32:20 +02:00