Skip to content

fix(org): tolerate foreign backup entries, restore overview scrolling, contain graph labels - #520

Merged
PeterGuy326 merged 4 commits into
mainfrom
fix/517-org-module-backup-tray-scroll-and-label-layering
Sep 29, 2026
Merged

PeterGuy326 merged 4 commits into
mainfrom
fix/517-org-module-backup-tray-scroll-and-label-layering

Conversation

@waterbro-8

Copy link
Copy Markdown
Collaborator

Tracking record

Closes #517

Reproduced on main at 15ad3ab, which is also this branch's base
(behind_by = 0, one commit ahead).

Summary

Three localized Organization-module defects, each fixed at the site the tracking
record identifies.

1. GET /org/backups 500s on foreign entries in the backup root.
listOrgBackups iterated every dirent of
<workspace>/.digital-employee/backup/ and treated it as a position backup. The
groups archive namespace that apps/server/src/groups/store.ts writes never
matches the position-backup id shape, so its mere presence failed the whole
request; the sidebar "Removed employees" tray therefore stayed in
tree.recoveryFailed with a retry that could never succeed, even with healthy
restorable backups beside it.

The per-entry loop now skips any entry whose name is not a position backup id,
and skips an entry that is nominally a backup but unrestorable (employee.json
missing, not a regular file, or symlinked; no dismissal provenance in
org-audit.jsonl) instead of failing the list. What still fails the request is
unchanged: a symlinked or non-directory runtime/backup root, and a symlinked or
non-directory entry whose name does parse as a position backup.
restoreOrgBackup is untouched - it addresses backups by explicit id.

The ordering is deliberate: an entry is classified by name first, so a
non-position name is ignored whatever its filesystem type. The only entries
restoreOrgBackup can ever reach are the ones that parse, so a foreign name is
never a traversal hazard, and rejecting it would reintroduce the fragility this
bug is about.

2. The overview page body cannot scroll. .owb-main is a flex column with
overflow: hidden, and in overview mode section.owb-rgraph is one of its flex
items (the wrapper in App.tsx uses display: contents). The rule that used to
size the overview root named .owb-org-chart--overview, a class the
relationship-graph refactor (#497) stopped emitting, so the section was left
unsized: its overflow was clipped by .owb-main and no scroll container existed
anywhere on the page. The wheel only ever reached OrbitControls on the canvas.

.owb-main > .owb-rgraph now owns the local scroll, replacing the dead
selectors. It also pins .owb-rgraph__workspace to its intrinsic height
(flex: none): the workspace is overflow: hidden, so its automatic minimum
size is 0 and an ordinary flex item would be squashed into the leftover height -
canvas silently clipped, still no scrollbar. scrollbar-gutter: stable keeps the
inline size (and therefore the @container rgraph (max-width: 940px) query that
swaps the inspector to an overlay) from flipping when the scrollbar appears. The
sticky inspector keeps working because it now sticks inside the new scroll
container.

3. CSS2D labels paint over the inspector and the canvas chrome. three's
CSS2DRenderer writes element.style.zIndex = zMax - i on every sorted label
(1..N for N visible labels; VISIBLE_LIMIT is 120). Nothing between
.owb-rgraph__spatial-labels and the workspace created a stacking context, so
those indices competed page-wide and beat the z-index: 3 toolbar / object strip
and the z-index: 8 "relationship details" overlay.

.owb-rgraph__spatial-stage now carries z-index: 0, which makes it a stacking
context: labels keep their in-canvas depth ordering, while the chrome, the WebGL
fallback and the inspector paint above the whole layer. No renderer-code change;
CSS2DRenderer.sortObjects keeps its default so label depth sorting inside the
canvas is preserved.

Out of scope: no change to the backup layout or the groups namespace, no new
route, no graph data-pipeline change, no renderer option change.

Acceptance criteria

Numbered in the order the tracking record lists them.

Acceptance criterion Status Implementation / evidence
AC-001 - with .digital-employee/backup/groups/ (or any non-position entry) present, GET /org/backups returns 200 and the tray lists the restorable position backups; a server test covers the foreign-namespace case PASS apps/server/src/org/restore.ts per-entry tolerance. apps/server/test/apply.test.ts -> org restore: skips foreign namespaces and unrestorable entries in the backup root seeds backup/groups/... plus an unparseable name, a non-regular employee.json and a well-named backup with no dismissal provenance beside one real restorable backup, then asserts the list is 200 and contains only community-operator, and that the restore still succeeds. V1, V2, V4
AC-002 - on the overview tab the page body scrolls (wheel, trackpad, keyboard) everywhere outside the 3D canvas; results list and footer are reachable; wheel over the canvas still zooms the camera PASS for the CSS contract; NOT VERIFIED for the measured gesture apps/desktop/renderer/src/app.css: .owb-main > .owb-rgraph + .owb-main > .owb-rgraph > .owb-rgraph__workspace. org-panel-sizing.test.cjs (#284) now resolves width / min-width / min-height / overflow-y on the live root and flex on the canvas column; scroll-containment.test.cjs asserts the section owns overflow-y: auto with a stable gutter. Canvas wheel-zoom is untouched: OrbitControls stays bound to the canvas, and the new scroll container is the section, not the canvas. V3, V7, V8
AC-003 - with a selection open at container width <= 940px the inspector overlay paints above all graph labels; at any width labels never cover .owb-rgraph__spatial-controls or .owb-rgraph__spatial-objects PASS for the CSS contract; NOT VERIFIED for the measured paint order apps/desktop/renderer/src/graph/RelationshipGraph.css stage z-index: 0. New apps/desktop/test/relationship-label-layering.test.cjs asserts the stage is positioned with z-index: 0 and that the chrome, the WebGL fallback and the <=940px inspector overlay each declare a strictly greater z-index. V3, V4, V8

Validation ledger

PASS / FAIL describe the check as actually run here; NOT VERIFIED marks
what this environment cannot execute.

# Command or check Expected Actual Evidence
V1 node node_modules/typescript/bin/tsc -b apps/server clean rc=0, no diagnostics; builds apps/server and the referenced packages/shared -
V2 node --test apps/server/dist/test/*.test.js no new failures 687 tests, 611 pass, 4 fail, 72 skipped. Pre-change baseline on the same tree and dependencies: 686 / 609 / 5 / 72. The failure set is a strict subset of the baseline set; the one extra test is the new one and it passes final-server.log, base-server.log
V3 node --test apps/desktop/test/*.test.cjs after a real vite build --config apps/desktop/vite.config.ts no new failures 358 tests, 339 pass, 5 fail, 14 skipped. Pre-change baseline: 356 / 337 / 5 / 14 - identical failure set. The 2 extra tests are new and both pass. Relevant results: ok 183 the organization overview owns a bounded canvas (#284), ok 235 + ok 236 (#517), ok 255 scrollable modules keep scrolling local to their own surface final-desktop.log, base-desktop.log
V4 Negative control: restore the pre-change listOrgBackups, app.css and RelationshipGraph.css one at a time and re-run each new or retargeted assertion must fail not ok 4 (server), not ok 7 (#284, both against the source stylesheet and against an esbuild-minified fixture), not ok 1 / not ok 2 (#517). Detail below -
V5 node --test scripts/test/package-layout.test.mjs scripts/test/package-safety.test.mjs only pre-existing environmental failures 19 tests, 16 pass, 1 fail: a third-party runtime dependency is either packaged or not required. That same test fails identically on a pristine 15ad3ab checkout, so it is environmental, not caused by this change pkg-safety.log
V6 node --test scripts/test/package-safety.test.mjs pass 10 tests, 8 pass, 0 fail -
V7 Inspect the built bundle for the new rules present verbatim after minification .owb-main>.owb-rgraph{flex:1;min-height:0;min-width:0;width:100%;box-sizing:border-box;overflow-x:hidden;overflow-y:auto;scrollbar-gutter:stable;overscroll-behavior:contain} and .owb-main>.owb-rgraph>.owb-rgraph__workspace{flex:none} in apps/desktop/dist/renderer/assets/index-*.css -
V8 AC-002 / AC-003 measured half: real wheel/keyboard scrolling and real paint order the page scrolls; the inspector paints above the labels NOT VERIFIED - needs a browser/layout engine, which this headless environment does not have. The repo already records the same limitation for #284 ("needs a layout engine, which jsdom is not"). Covered by CI verify.yml job check (ubuntu-latest + macos-14, Node 24, npm run check) and by the packaged-layout smoke jobs -

Negative-control detail (V4):

Control Reverted artifact Result
Pre-change listOrgBackups semantics apps/server/dist/src/org/restore.js patched back to throw per entry not ok 4 - org restore: skips foreign namespaces and unrestorable entries in the backup root; 14 pass / 1 fail. Restored -> 15 pass / 0 fail
Pre-change overview sizing app.css as of 15ad3ab not ok 7 - the organization overview owns a bounded canvas (#284), expected: '100%', actual: ~; 7 pass / 1 fail. Restored -> 8 pass / 0 fail. Reproduced against an esbuild-minified fixture as well
Pre-change label layering RelationshipGraph.css as of 15ad3ab not ok 1, not ok 2; 0 pass / 2 fail. Restored -> 2 pass / 0 fail

Tests and coverage

  • Tests added:
    • apps/server/test/apply.test.ts -> org restore: skips foreign namespaces and unrestorable entries in the backup root (AC-001).
    • apps/desktop/test/relationship-label-layering.test.cjs, 2 assertions (AC-003).
  • Tests changed:
    • apps/desktop/test/org-panel-sizing.test.cjs - the fix(desktop): restore readable theme colors and centered control labels #284 assertion block now resolves against .owb-main > .owb-rgraph instead of the dead .owb-org-chart--overview, and gained the scroll-container and canvas-column assertions for AC-002.
    • apps/desktop/test/scroll-containment.test.cjs - .owb-main > .owb-rgraph joins the local-scroll-surface guard.
  • Coverage before / after: not measured. This repository does not gate on a coverage number and npm run check has no coverage threshold wired in.
  • Intentionally uncovered: the measured half of AC-002 and AC-003 (V8). It needs a layout engine; the existing CSS-contract guards are the executable floor and the packaged smoke jobs are the visual gate.

Change classification

  • User-visible behavior
  • Internal refactor or maintenance
  • Documentation only
  • Build, CI, dependency, or repository configuration
  • Breaking change
  • Security-sensitive change

Risk and rollback

  • Risk level: low. Three localized changes - one fault-tolerance branch in a list
    handler, two CSS rules and one CSS declaration. No API shape, persistence
    format, route or renderer-code change.
  • Affected components: apps/server organization backup listing; the desktop
    renderer's Organization module (overview tab) and its guard tests.
  • Compatibility, migration, privacy, performance, operational impact: none.
    GET /org/backups keeps its org-backups.v1 shape and can only return fewer
    500s; the listing performs the same number of lstat calls. Making the
    overview section a scroll container adds no listeners or timers.
  • Rollback procedure: revert this single commit. It is self-contained and
    depends on no data written by the new code.

Breaking or security notes

Not a breaking change. On security, the fail-closed predicates are deliberately
preserved rather than relaxed: a symlinked or non-directory runtime or backup
root still fails the request, and an entry that parses as a position backup but
is a symlink or a non-directory still fails. Only entries restoreOrgBackup
cannot reach (foreign names) and entries that could never be restored (no
regular employee.json, no dismissal provenance) move from "fail the whole
request" to "skip".

Author checklist

  • A maintainer confirmed that the linked issue or tracking record was ready before implementation began, or the automation was pre-authorized under GOVERNANCE.md.
  • This branch was created from an up-to-date default branch and contains no unrelated changes.
  • I ran the repository's applicable tests, lint, type checks, builds, coverage, and security checks.
  • I added a regression test for a bug fix, or explained why one is impractical.
  • I updated relevant documentation and changelog files.
  • I reviewed the diff for secrets, personal data, generated artifacts, and dependency risk.
  • The PR is ready for CI and review by someone other than the sole author.
  • All reported results are reproducible.

The first box is intentionally left unticked: #517 is still status: reported
and no maintainer has confirmed it ready. Everything else is ticked, with V8
honestly recorded as NOT VERIFIED rather than asserted.

Reviewer notes

  • The name-before-type ordering in listOrgBackups is the one judgement call
    worth scrutinising. It means a symlink named groups is skipped rather than
    failing the request, while a symlink named <positionId>-<stamp>-<hex> still
    fails. If the intended reading is "any symlink anywhere under the backup root
    must fail", the two checks in the loop can simply be swapped;
    restoreOrgBackup cannot reach a foreign name either way.
  • flex: none on .owb-rgraph__workspace is load-bearing, not cosmetic.
    Without it the workspace shrinks into the leftover height (its
    overflow: hidden gives it an automatic minimum size of 0), so the canvas is
    clipped instead of scrolled and the reported symptom survives even though a
    scroll container now exists. The new fix(desktop): restore readable theme colors and centered control labels #284 assertion pins it.
  • Independent reproduction: after npm run build,
    node --test apps/server/dist/test/apply.test.js covers AC-001 end to end.
    For AC-002 and AC-003, run npm run build:renderer and then
    node --test apps/desktop/test/*.test.cjs; the measured gesture and paint
    order need a real window and are best checked on the packaged app.

…, contain graph labels

`GET /org/backups` treated every dirent of the backup root as a position
backup, so the `groups` archive namespace alone made the request fail with 500
and left the sidebar recovery tray retrying forever. Skip entries whose name
is not a position backup id, and skip an individually broken backup (missing
or irregular employee.json, absent dismissal provenance) instead of failing
the whole list. Unsafe filesystem predicates still fail the request.

The overview tab mounts `section.owb-rgraph` directly into the `.owb-main`
flex column, but the rule that used to size it named `.owb-org-chart--overview`
(a class the relationship-graph refactor stopped emitting). The section was
left unsized, `.owb-main`'s `overflow: hidden` clipped everything below the
fold, and the page had no scroll surface at all. Size the live overview root
as the local scroll container and keep the canvas column at its intrinsic
height so it overflows the section instead of being squashed into it.

three's CSS2DRenderer writes a per-label z-index, and no wrapper between the
label layer and the workspace formed a stacking context, so the labels painted
over the z-index 3 canvas chrome and the z-index 8 details overlay. Give the
label stage `z-index: 0` to contain them while preserving in-canvas depth.

Guards: the `#284` overview-sizing assertions are retargeted from the dead
class to the live `.owb-rgraph` root and extended with the scroll contract;
`.owb-main > .owb-rgraph` joins the local-scroll-surface guard; and a new
`relationship-label-layering.test.cjs` pins the stacking contract.

Closes #517
@waterbro-8

Copy link
Copy Markdown
Collaborator Author

CI note for reviewers.

The first Node 24 / macos-14 run failed inside test:renderer:

FAIL  renderer/test/App.test.tsx > returns to the workbench after leaving the organization module
Error: Test timed out in 5000ms.
Test Files  1 failed | 68 passed (69)
Tests  1 failed | 753 passed (754)

That is a plain 5 s timeout, not an assertion failure, in a file this change does not touch (the renderer changes here are CSS only, and jsdom does not apply them). The whole App.test.tsx file took 117 s on that runner, so the box was heavily loaded. Re-running the failed job turned it green, and the Node 24 / ubuntu-latest leg passed the identical suite on the first attempt.

All 11 checks are green now.

…ing (keep PR-side labeled stacking rule; main already carries the same z-index: 0 fix)
PeterGuy326
PeterGuy326 previously approved these changes Sep 29, 2026

@PeterGuy326 PeterGuy326 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second reviewer approval on head 38b4dbac (merge commit resolving the label-stacking conflict against main; the PR side keeps the commented rule, which is functionally identical to what #523 already landed).

What I verified independently, not just from the PR description:

  1. listOrgBackups (apps/server/src/org/restore.ts): the backup/groups namespace collision is real — parseBackupId returns null for foreign names and the old code 500'd the whole tray. Skipping foreign names while still hard-failing on entries that claim the position-backup pattern but have unsafe filesystem state is the right split. Broken packages and missing dismissal provenance degrade to skipped entries instead of hiding healthy backups.
  2. Scroll contract: confirmed the overview mounts section.owb-rgraph under .owb-main through a display: contents wrapper, so the old .owb-org-chart--overview rules were dead since the relationship-graph refactor. overflow-y: auto + scrollbar-gutter: stable + overscroll-behavior: contain matches the contract every other module root follows (asserted by the extended scroll-containment.test.cjs), and flex: none on the canvas column is needed so the overflow: hidden column is not squashed by its automatic minimum size.
  3. Label containment: the CSS2DRenderer zMax−i z-index escape is contained by z-index: 0 on the stage; relationship-label-layering.test.cjs pins the stacking contract including the chrome/inspector z-order relations.

No blocking findings. Not product acceptance.

….owb-rgraph__object-navigation in the label-layering contract
@PeterGuy326

Copy link
Copy Markdown
Contributor

Holding the merge on this one, with the current state on record:

  • Review: approved (see my earlier review on head 38b4dbac); the #523 rename of .owb-rgraph__spatial-objects → .owb-rgraph__object-navigation was followed in the label-layering contract test (head 4971a1b), and that test now passes.
  • Conflicts: resolved against current main and pushed.
  • Remaining red checks are not caused by this branch: fresh runs of main itself (rerun of 36439924948, head f2fca7b) now fail the required Node 24 jobs on both platforms with the same App.test.tsx toolbar-test timeout. Tracked in ci(renderer): App context navigation toolbar test times out on fresh runs of main (both platforms) #529. One earlier staging-smoke failure was a Windows EBUSY cleanup flake and passed on rerun.

Once #529 is fixed on main, this branch should go green without further changes and can be merged (approval already recorded).

PeterGuy326 added a commit that referenced this pull request Sep 29, 2026
…#531)

The full-App toolbar test sits above the 5s vitest default on slower runners and failed fresh runs of main on both platforms (#529), blocking #520/#522/#524/#527 through no change of their own. 15s matches the other full-App tests in this file. Closes #529.

@PeterGuy326 PeterGuy326 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving on head a68c78e (synced with main @ bd514ae). My earlier approval covered the diff through 38b4dba; the only change since was the test-only rename follow-up (4971a1b) and this sync, and the required checks are green on this head.

@PeterGuy326
PeterGuy326 merged commit ed94fca into main Sep 29, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Org module: removed-employee tray 500s on foreign backup entries, overview page cannot scroll, graph labels paint over details inspector

2 participants