Repository navigation
fix(org): tolerate foreign backup entries, restore overview scrolling, contain graph labels - #520
Conversation
…, 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
|
CI note for reviewers. The first 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 All 11 checks are green now. |
…ing (keep PR-side labeled stacking rule; main already carries the same z-index: 0 fix)
PeterGuy326
left a comment
There was a problem hiding this comment.
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:
listOrgBackups(apps/server/src/org/restore.ts): thebackup/groupsnamespace collision is real —parseBackupIdreturns 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.- Scroll contract: confirmed the overview mounts
section.owb-rgraphunder.owb-mainthrough adisplay: contentswrapper, so the old.owb-org-chart--overviewrules were dead since the relationship-graph refactor.overflow-y: auto+scrollbar-gutter: stable+overscroll-behavior: containmatches the contract every other module root follows (asserted by the extendedscroll-containment.test.cjs), andflex: noneon the canvas column is needed so theoverflow: hiddencolumn is not squashed by its automatic minimum size. - Label containment: the CSS2DRenderer zMax−i z-index escape is contained by
z-index: 0on the stage;relationship-label-layering.test.cjspins 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
|
Holding the merge on this one, with the current state on record:
Once #529 is fixed on main, this branch should go green without further changes and can be merged (approval already recorded). |
PeterGuy326
left a comment
There was a problem hiding this comment.
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.
Tracking record
Closes #517
Reproduced on
mainat15ad3ab, 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/backups500s on foreign entries in the backup root.listOrgBackupsiterated every dirent of<workspace>/.digital-employee/backup/and treated it as a position backup. Thegroupsarchive namespace thatapps/server/src/groups/store.tswrites nevermatches the position-backup id shape, so its mere presence failed the whole
request; the sidebar "Removed employees" tray therefore stayed in
tree.recoveryFailedwith a retry that could never succeed, even with healthyrestorable 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.jsonmissing, not a regular file, or symlinked; no dismissal provenance in
org-audit.jsonl) instead of failing the list. What still fails the request isunchanged: a symlinked or non-directory runtime/backup root, and a symlinked or
non-directory entry whose name does parse as a position backup.
restoreOrgBackupis 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
restoreOrgBackupcan ever reach are the ones that parse, so a foreign name isnever a traversal hazard, and rejecting it would reintroduce the fragility this
bug is about.
2. The overview page body cannot scroll.
.owb-mainis a flex column withoverflow: hidden, and in overview modesection.owb-rgraphis one of its flexitems (the wrapper in
App.tsxusesdisplay: contents). The rule that used tosize the overview root named
.owb-org-chart--overview, a class therelationship-graph refactor (#497) stopped emitting, so the section was left
unsized: its overflow was clipped by
.owb-mainand no scroll container existedanywhere on the page. The wheel only ever reached
OrbitControlson the canvas..owb-main > .owb-rgraphnow owns the local scroll, replacing the deadselectors. It also pins
.owb-rgraph__workspaceto its intrinsic height(
flex: none): the workspace isoverflow: hidden, so its automatic minimumsize is 0 and an ordinary flex item would be squashed into the leftover height -
canvas silently clipped, still no scrollbar.
scrollbar-gutter: stablekeeps theinline size (and therefore the
@container rgraph (max-width: 940px)query thatswaps 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
CSS2DRendererwriteselement.style.zIndex = zMax - ion every sorted label(1..N for N visible labels;
VISIBLE_LIMITis 120). Nothing between.owb-rgraph__spatial-labelsand the workspace created a stacking context, sothose indices competed page-wide and beat the
z-index: 3toolbar / object stripand the
z-index: 8"relationship details" overlay..owb-rgraph__spatial-stagenow carriesz-index: 0, which makes it a stackingcontext: 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.sortObjectskeeps its default so label depth sorting inside thecanvas is preserved.
Out of scope: no change to the backup layout or the
groupsnamespace, no newroute, no graph data-pipeline change, no renderer option change.
Acceptance criteria
Numbered in the order the tracking record lists them.
.digital-employee/backup/groups/(or any non-position entry) present,GET /org/backupsreturns 200 and the tray lists the restorable position backups; a server test covers the foreign-namespace caseapps/server/src/org/restore.tsper-entry tolerance.apps/server/test/apply.test.ts->org restore: skips foreign namespaces and unrestorable entries in the backup rootseedsbackup/groups/...plus an unparseable name, a non-regularemployee.jsonand a well-named backup with no dismissal provenance beside one real restorable backup, then asserts the list is 200 and contains onlycommunity-operator, and that the restore still succeeds. V1, V2, V4apps/desktop/renderer/src/app.css:.owb-main > .owb-rgraph+.owb-main > .owb-rgraph > .owb-rgraph__workspace.org-panel-sizing.test.cjs(#284) now resolveswidth/min-width/min-height/overflow-yon the live root andflexon the canvas column;scroll-containment.test.cjsasserts the section ownsoverflow-y: autowith a stable gutter. Canvas wheel-zoom is untouched:OrbitControlsstays bound to the canvas, and the new scroll container is the section, not the canvas. V3, V7, V8.owb-rgraph__spatial-controlsor.owb-rgraph__spatial-objectsapps/desktop/renderer/src/graph/RelationshipGraph.cssstagez-index: 0. Newapps/desktop/test/relationship-label-layering.test.cjsasserts the stage is positioned withz-index: 0and that the chrome, the WebGL fallback and the <=940px inspector overlay each declare a strictly greaterz-index. V3, V4, V8Validation ledger
PASS/FAILdescribe the check as actually run here;NOT VERIFIEDmarkswhat this environment cannot execute.
node node_modules/typescript/bin/tsc -b apps/serverrc=0, no diagnostics; buildsapps/serverand the referencedpackages/sharednode --test apps/server/dist/test/*.test.jsfinal-server.log,base-server.lognode --test apps/desktop/test/*.test.cjsafter a realvite build --config apps/desktop/vite.config.tsok 183 the organization overview owns a bounded canvas (#284),ok 235+ok 236(#517),ok 255 scrollable modules keep scrolling local to their own surfacefinal-desktop.log,base-desktop.loglistOrgBackups,app.cssandRelationshipGraph.cssone at a time and re-runnot 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 belownode --test scripts/test/package-layout.test.mjs scripts/test/package-safety.test.mjsa third-party runtime dependency is either packaged or not required. That same test fails identically on a pristine15ad3abcheckout, so it is environmental, not caused by this changepkg-safety.lognode --test scripts/test/package-safety.test.mjs.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}inapps/desktop/dist/renderer/assets/index-*.cssverify.ymljobcheck(ubuntu-latest + macos-14, Node 24,npm run check) and by the packaged-layout smoke jobsNegative-control detail (V4):
listOrgBackupssemanticsapps/server/dist/src/org/restore.jspatched back to throw per entrynot ok 4 - org restore: skips foreign namespaces and unrestorable entries in the backup root; 14 pass / 1 fail. Restored -> 15 pass / 0 failapp.cssas of15ad3abnot 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 wellRelationshipGraph.cssas of15ad3abnot ok 1,not ok 2; 0 pass / 2 fail. Restored -> 2 pass / 0 failTests and coverage
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).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-rgraphinstead 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-rgraphjoins the local-scroll-surface guard.npm run checkhas no coverage threshold wired in.Change classification
Risk and rollback
handler, two CSS rules and one CSS declaration. No API shape, persistence
format, route or renderer-code change.
apps/serverorganization backup listing; the desktoprenderer's Organization module (overview tab) and its guard tests.
GET /org/backupskeeps itsorg-backups.v1shape and can only return fewer500s; the listing performs the same number of
lstatcalls. Making theoverview section a scroll container adds no listeners or timers.
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
restoreOrgBackupcannot reach (foreign names) and entries that could never be restored (no
regular
employee.json, no dismissal provenance) move from "fail the wholerequest" to "skip".
Author checklist
GOVERNANCE.md.The first box is intentionally left unticked: #517 is still
status: reportedand no maintainer has confirmed it ready. Everything else is ticked, with V8
honestly recorded as
NOT VERIFIEDrather than asserted.Reviewer notes
listOrgBackupsis the one judgement callworth scrutinising. It means a symlink named
groupsis skipped rather thanfailing the request, while a symlink named
<positionId>-<stamp>-<hex>stillfails. If the intended reading is "any symlink anywhere under the backup root
must fail", the two checks in the loop can simply be swapped;
restoreOrgBackupcannot reach a foreign name either way.flex: noneon.owb-rgraph__workspaceis load-bearing, not cosmetic.Without it the workspace shrinks into the leftover height (its
overflow: hiddengives it an automatic minimum size of 0), so the canvas isclipped 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.
npm run build,node --test apps/server/dist/test/apply.test.jscovers AC-001 end to end.For AC-002 and AC-003, run
npm run build:rendererand thennode --test apps/desktop/test/*.test.cjs; the measured gesture and paintorder need a real window and are best checked on the packaged app.