Conversation
…og (#235) * (moduleRegistry) Turn module map into a declarative role catalog Replace the {id -> component} map with MODULE_CATALOG, where each module carries a placement role (main/side/bottom) plus metadata (singleton, solo, step, keepMounted). MODULE_COMPONENTS and MODULE_TITLES are derived from the catalog so existing callsites keep working. Add getModuleRole() and isSingletonModule() (singleton defaults true). * (placement) Dock new modules by role, not the active tabset openModule() resolved its target via resolveTargetTabset, which prefers the active tabset — so the same command placed a module differently depending on where the user last clicked (the file queue could land in a narrow 15% side column). Add a pure resolvePlacementTabset(model, role): main -> largest tabset (weight fallback before first measure), side -> existing narrow left column or a LEFT split, bottom -> existing bottom bar or a BOTTOM split. openModule looks up the module role and applies the placement descriptor, with an options.placement escape hatch for callers that force a target. SINGLETON_MODULES is derived from the registry instead of a hand-held list. resolveTargetTabset is unchanged for the active-tabset (keyboard-ownership) choice. * (docs) Note role catalog + deterministic placement Record the module role/keepMounted foundation under the FileQueue companion roadmap entry, and add an Unreleased Changed note for role-based tab placement. * (workspace) Update stale handleOpenCulling ordering comment The open-before-close comment justified the order via the active tabset (getActiveTabset → undefined), a dependency removed when openModule switched to role-based placement. Reword it to the actual remaining reason: opening culling first guarantees a host tabset before closing Review could empty the layout. * (placement) Require measured rects before reusing a bottom bar FlexLayout's getRect() returns a zero-sized Rect (not null) before the first render/measure pass, so the bottom-role branch's truthy guard let threshold become 0 and matched every other unmeasured tabset at y === 0 (e.g. the left Review column) — stacking Log Viewer into the side panel instead of splitting a bottom bar. Require non-zero width/height on both the main area and the candidate (isMeasured); until measured, fall back to a BOTTOM split. Add a headless test for the all-zero-rect case. * (placement) Size a fresh split to the module's role weight A new LEFT/BOTTOM split gets FlexLayout 0.8.17's default sizing, which halves the target — so a side/bottom module opened from a solo workspace landed at 50% despite its catalog weight (15/20), and could end up weighing >= the main area, making later role resolution (main picked by weight) ambiguous. Add applyPlacement() in tabsetUtils, which pins the new tabset to the module's declared weight and gives the rest to main; openModule now routes through it with getModuleWeight(moduleId). Give log-viewer an explicit weight (20) so every split-producing role carries one. * (test) Add placement invariant matrix (role x layout state) Consolidating matrix that enumerates role (main/side/bottom) x layout state (measured/unmeasured, target present/absent, solo) and asserts both the resolved placement descriptor and that a resulting split never weighs the new side/bottom pane >= main — so role resolution stays unambiguous after a split. Closes the placement-edge-case class. * (placement) Gate side/bottom reuse on hosting a same-role module The reuse predicates matched on size alone: any tabset narrower than main counted as a side column, any measured tabset below main as a bottom bar. With the Database layout (database-management weight 30 beside statistics 70) that hijacked the narrow Database column — file-queue/review docked onto it, hiding Database — instead of splitting a fresh side pane. Gate reuse on the tabset already HOSTING a module of the wanted role, looked up via an injected roleOf(componentId) so tabsetUtils stays React-free; the workspace passes getModuleRole. Mirror the same gate for bottom-bar reuse (a panel below main hosting a non-bottom module is not a bottom bar). No side/bottom-hosting tabset → fresh split, as before. * (test) Cover role-hosting reuse gate in placement matrix Extend the fixtures with occupant components and pass roleOf. New cells: Database 30/70 narrow non-side column → split not hijack; a genuine side-module tabset → reuse; a measured panel below main hosting a non-bottom module → split; and reuse disabled when roleOf is omitted. Also assert a freshly split side pane becomes reusable next time.
* (workflowSteps) Extract shared pipeline-step derivation from landing
Pull the workflow step list, tools list and continue-where-you-left-off
mapping out of StartupLanding into workspace/flexlayout/workflowSteps.js,
deriving each step's module id from the catalog's step metadata (add
getModuleStep to the registry). StartupLanding now consumes the shared
module so the WorkflowBar can render the same steps from one source.
* (workflow-bar) Add persistent WorkflowBar above the layout
Slim, always-visible pipeline row (Importera → Byt namn → Granska →
Räkna → Gallra) rendered by FlexLayoutWorkspace above the FlexLayout
host. Highlights the active step (tracked in activeStep, set by step
clicks and the existing hand-off events), shows a working-folder chip
with a Continue button, and a Verktyg menu for non-pipeline modules.
Steps/tools come from the shared workflowSteps catalog. Gated by the new
workspace.showWorkflowBar preference (default on), toggle in Layout
prefs; the bar re-reads on preferences-changed.
* (docs) Add ux-principles, document WorkflowBar
New docs/dev/ux-principles.md codifies the applied Nielsen heuristics,
navigation rules and style rules behind the pipeline navigation; linked
from CLAUDE.md Related Docs. Add a workspace-guide section on the
workflow bar, a CHANGELOG Added entry, and ROADMAP updates (nav-series
status + the resolvePlacementTabset/getMaximizedTabset known issue).
* (workflow-bar) Guard step switch against unsaved Review edits
A WorkflowBar step click runs openLandingStep, which replaces the layout
model and unmounts a live ReviewModule — dropping its unsaved
confirmations/ignores past the reviewDirtyRef guard the culling hand-off
honors. Reuse that same signal: openWorkflowStep now confirms via the
shared useConfirm primitive when Review is dirty and clears the dirty set
on a confirmed discard; cancel leaves the layout untouched. Expose
openWorkflowStep on window.workspace and pin the guard with tests.
* (WorkflowBar) Stop clipping the Verktyg dropdown
overflow:hidden on .workflow-bar clipped the absolutely-positioned tools
menu, making it invisible. Drop it; clip the step group itself instead so
the chip/tools stay visible when narrow, and give the bar a positioned,
lifted stacking context so the dropdown paints above the FlexLayout host
that follows it in the DOM.
* (workflow-bar) Mark Review active for the queue-files launch path
The bare `ansikten -q FILER` launch (handleQueueFilesIpc) brought up the
review pipeline without setting activeStep, so the WorkflowBar highlighted
no step for that entry. Set activeStep('review') at the head of the
handler, matching the other hand-off handlers.
* (WorkflowBar) Fix stale header comment on step opening path
onOpenStep is wired to openWorkflowStep (the dirty-guarded wrapper), not
openLandingStep directly.
* (workflow-bar) Treat active-step click as focus, not reload
Clicking the already-active step reached the dirty-prompt + openLandingStep
reload path, so a dirty Review would prompt and lose its edits to a
queue-review reload even though the user never left the step. Short-circuit
in openWorkflowStep: when the clicked step equals activeStep, focus its
surface via openModule and return before the guard. Test pins that an
active-step click while dirty neither confirms nor rebuilds the layout.
* (workflow-bar) Gate active-step focus no-op on module presence
The active-step focus fast-path fired even when activeStep had gone stale
— the user closed the step's tabs or loaded another layout from the menu
while activeStep lingered on 'review'. openModule then opened a bare
Review with no queue/viewer (a dead end). Gate the no-op on the step's
module still being in the model (hasModuleTab); when its tab is gone, fall
through to the full path so openLandingStep rebuilds the surface. Test
pins the stale-activeStep divergence; the genuine no-op regression test
stays green.
* (workflow-bar) Never discard-prompt on an already-mounted step
Starting in the default/reset Review layout activeStep is null, so a
click on Granska with unsaved edits missed the focus fast-path and ran
the dirty prompt + queue-review rebuild — discarding edits for the step
the user was already on. Broaden the fast-path: focus (no rebuild) when
the clicked step's module is mounted AND (it's the active step OR Review
is dirty). A clean click on a mounted-but-not-active step still falls
through to build the canonical surface (no queue-less dead end), and a
stale activeStep whose tab is gone still rebuilds. Tests pin both new
cases; the no-op and divergence tests stay green.
* (workflow-bar) Sync bar highlight when fast-path focuses via dirty clause
The focus fast-path didn't call setActiveStep, so when it fired through
the dirty clause with step !== activeStep (cold start: activeStep null,
dirty click on a mounted step), the bar marking stayed stale. Set
activeStep(step) alongside the focus so the highlight follows focus
regardless of dirty state.
* (workflowSteps) Normalize Windows separators in basename
The folder picker / backend can return C:\events\cupen; basename only
split on '/', so the whole path showed in the working-folder chip instead
of the folder name. Convert backslashes to '/' before deriving the last
segment. The app ships on Windows.
* (workflow-bar) Derive activeStep in openModule from step metadata
View-menu / Verktyg-menu commands route straight through openModule, which
never synced the bar highlight, so opening Räkna/Gallra/Import left the row
blank or pointing at the previous step. Sync at the root: after a successful
open/focus, openModule reads the module's step from the catalog and marks it
active; stepless modules (Inställningar, Statistik, Loggar) leave activeStep
untouched. This covers the menu, the Verktyg menu and window.workspace
callers in one place, so the three hand-off handlers that route through
openModule drop their now-redundant setActiveStep; the loadLayout-path
handlers (review-queue, queue-files) and the openWorkflowStep full path keep
theirs. Expose activeStep on window.workspace; tests pin step + stepless.
…he model (#237) * (workspace-morph) Spike: verify moveNode preserves component state flexlayout-react 0.8.17: Actions.moveNode retains the moved tab's node id, so React keeps the same component instance across the move — state survives both a move into an existing tabset and into a fresh split. This is the load-bearing assumption for the morphing engine (no park-in-border fallback needed). * (workspace-morph) Add declarative step specs + morphing engine workflows.js declares each pipeline step's pane spec (solo modules; review = file-queue|review|image-viewer at 15|15|70) derived from the role catalog. workspaceMorph.applyWorkspace reshapes the LIVE model into a spec over FlexLayout Actions (moveNode/addNode/deleteTab/updateNodeAttributes) so mounted modules keep their state; keepMounted (File Queue) and dirty (Review) modules are parked in a collapsed bottom border instead of being closed. layouts.js gains ensureBottomBorder so every model has a border to park into. * (workspace-morph) Wire morphing engine into step navigation enterStep(stepId) morphs the live model via applyWorkspace and is the sole structural layout-change path; openWorkflowStep keeps PR 2's pinned dirty/mounted fast-path but its rebuild branch now morphs instead of replacing the model, so a step switch no longer discards a dirty Review — the discard prompt is gone (kept only on Reset layout, the one destructive rebuild). CLI/ hand-off IPC (culling/import/rename/review/queue-files) and FileQueue.loadFile route through enterStep; openModuleSolo/openLandingStep/ensureReviewSurface/ ensureQueueMounted are removed. Cmd+1..5 now drive the pipeline steps; the old layout templates move to an accelerator-free Window submenu. * (workspace-morph) Test step specs, Cmd routing and morph round-trip workflows.test.js pins the spec shapes/order and primaryModuleOf; menuCommandsSteps.test.js pins Cmd+1..5 → openWorkflowStep routing and the dirty-guarded reset-layout; flexLayoutWorkspace gains a review→culling→review round-trip asserting the File Queue node id survives the park/un-park. * (workspace-morph) Document morphing navigation + Cmd+1-5 remap ux-principles rule 3 now describes the realized morphing engine (enterStep / applyWorkspace, background parking, no discard prompt on step switch); the workspace guide documents Cmd+1-5 stepping and the moved layout templates; CHANGELOG [Unreleased] and ROADMAP record the change including the Cmd+1-5 muscle-memory break. Drops two unused test imports flagged by eslint. * (workspace-morph) Keep the active pane on idempotent re-entry applyWorkspace no longer forces the primary pane active when the currently active tabset is already one of the step's panes. Without this, FileQueue's enterStep('review') on every file load would yank keyboard focus from the Review panel to the Image Viewer. A cross-step morph (active pane gone) still elects the primary. * (workspace-morph) Label parked tabs by module for distinguishability A parked tab is now named "Bakgrund: <module>" (e.g. "Bakgrund: Filkö") so several modules parked at once stay distinct in the background border. * (workspace-morph) Guard parking state-preservation at the React mount level workspaceMorph.test.js proves parking via the node-id proxy; this adds a mount-level round-trip (stateful probe in the review trio → count parks it → review un-parks it) asserting mount-count stays 1 and internal state survives. Pins the invariant directly, so a regression in FlexLayout's per-tab render-on-demand honoring in a collapsed border would fail here even if the node-id proxy still passed. * (menu) Drop the mislabeled Helbild layout template The Helbild item never mapped to a full-image layout — its action loaded the review layout — and a full image is already reachable via Visa → Bildvisare (Cmd+,), so it was a confusing duplicate among the moved secondary templates. Removed; Jämförelse and Statistik remain with accurate labels.
…off polish (#238) * (scanScope) Add display-only subscribeScanScope * (queueStatus) Publish queue snapshot + label source folder in header * (workflow-chain) Complete Fortsätt chain review to count to culling * (workflow-bar) Working-set status dropdown on the folder chip * (handoff) Standardize step hand-off buttons to "Nästa steg: <verb> →" * (docs) Document working-set chip, queue labelling and hand-off chain * (workflow-chain) Skip Räkna adopt-on-mount when open-count signals external load * (workflow-bar) Use role=group for the chip status popover, not role=menu * (workflow-chain) Consume external-load flag in count-load to close the already-mounted leak
…CLI fix (#239) * (launch-command) Resolve CLI launch to one workspace command post-expansion Pure, filesystem-free decision (expanders injected) turning a parsed CLI intent into a single workspace command. The decision is made AFTER path expansion, so a path that matches nothing still yields an explicit command (open the step empty) instead of leaving the renderer to guess landing suppression from raw argument counts and strand in the default layout. * (workspaceCommands) Add the single workspace command router Typed-intent router with an intent buffer: intents dispatched before the workspace signals ready are queued and flushed in order on markReady (the "router not up yet" race). Hand-off intents (queue-files, open-culling, open-count, open-import, open-rename-nef, open-review-queue, load-image) share one morph + waitForListeners + emit implementation, so the cold-mount guard (the "target module not mounted yet" race) is written once and behaves identically for every hand-off. signalExternalLoad for the count hand-off is preserved via the HANDOFFS descriptor. * (workspace-router) Route all opens through the router + ready handshake Consolidate the three "open" mechanisms and the CLI launch bridge into the single command router. The menu dispatch table, window.workspace, and the in-app moduleAPI open-* events are now thin adapters that build a typed intent and call dispatch; the router owns routing and the per-hand-off waitForListeners guards. Cold-start launch now uses a workspace-ready handshake instead of a timer: the renderer signals workspace-ready once its router and listeners are live, and the main process holds its resolved launch command until then, sent over one unified workspace-command IPC. This replaces the did-finish-load + 1000ms setTimeout timing lottery (removed). The renderer no longer guesses landing suppression from raw argument counts — it reads the post-expansion willLaunch boolean — and a CLI path that expands to nothing opens the target step empty (e.g. `ansikten culling /typo`) rather than stranding in the default layout. Guards removed and what replaces them: - did-finish-load + 1000ms setTimeout (x3: culling/import/queue): replaced by the deterministic workspace-ready handshake (main sends only after the renderer's listeners are live). - Renderer raw-arg landing guess (hasFiles/clear/verb): replaced by the main process's post-expansion willLaunch boolean. Guards kept verbatim (relocated into the router): the per-hand-off waitForListeners cold-mount guard (buffer does not cover lazy module mount), the review-dirty/keepDirty morph semantics, and signalExternalLoad for count. * (docs) Document the command router, handshake and CLI empty-expansion fix Strike the resolved CLI empty-expansion roadmap item; add CHANGELOG entries (Changed: router consolidation + workspace-ready handshake; Fixed: CLI path that expands to nothing opens the step empty instead of stranding). Add navigation rule 6 (one router opens everything; handshake not timer; launch decided post-expansion) to ux-principles, and note the CLI empty-path behaviour in the user workspace guide. * (launch-queue) FIFO hold-until-ready queue for launch commands Pure (Electron-free, DI delivery) queue that holds workspace commands until the renderer completes the workspace-ready handshake. It is a FIFO queue, not a single slot, so an initial CLI launch and a second-instance launch that both arrive before ready deliver in order instead of the second clobbering the first. markNotReady() re-arms the hold (queue preserved) so a caller can stash commands across a renderer reload. * (launch) Queue launch commands and re-arm the hold across renderer reloads Replace the single launchCommand slot + workspaceReady flag with the FIFO launch queue. Two robustness fixes: - Renderer reload (Cmd+R / crash) previously left workspaceReady stuck true, so a second-instance command mid-reload was sent into a router-less page and dropped. markNotReady() on webContents 'did-start-loading' re-arms the hold; the command queues and delivers on the next workspace-ready (the renderer re-signals per mount). - A second launch arriving before the first handshake overwrote the initial launch (single slot). The FIFO queue delivers both in order.
…doc sweep (#240) * (startup-landing) Trim to a welcome card; bar owns navigation The always-visible WorkflowBar already provides the pipeline steps, the working-set chip, the Fortsatt continuation and the Verktyg tools menu at all times — including on an empty workspace. Re-rendering the step grid, tools grid and continue row on the landing only duplicated the bar (the double-Fortsatt-buttons problem). Reduce StartupLanding to a pure welcome/orientation card: title, subtitle and a hint pointing at the bar. It no longer needs backend, moduleAPI or working-folder wiring. The bar is the single source for navigation and continuation. * (step-layout-memory) Remember each step's layout across switches Each pipeline step (import/rename/review/count/culling) now remembers the shape the user leaves it in — an extra pane, changed pane weights — and restores it on re-entry instead of the bare factory layout (Capture One workspaces pattern, Nielsen N7). stepLayoutMemory.js stores one pane spec per step under ansikten-workspace-<step>. A spec ({ moduleId, weight }[]) — not a full model — feeds the existing non-destructive morph, so restoring a remembered layout preserves live module state and the parked-in-background semantics. A snapshot reads only real tabsets, so border-parked Bakgrund tabs never leak into a step's memory. resolveStepSpec merges saved with factory so a step's essential modules can never go missing. Persistence is scoped and settled: handleModelChange writes the active step's spec on real user changes only (programmatic morphs are suppressed); changes with no active step are not remembered. Mount always builds the neutral default preset — memory surfaces only when a step is entered. The legacy ansikten-flexlayout key is migrated once into review's memory, then removed. Reset layout now forgets the current step's memory and rebuilds it to factory; new Reset all layouts (Window menu) forgets every step. Both keep the dirty-Review confirm. * (cleanup) Drop dead layout commands and unused fullImage key The command-table entries layout-full-review and layout-template-full-image are inert — no menu item dispatches them any more (Helbild was removed from the Layoutmallar submenu when the workflow bar took over Cmd+1..5). Remove them and the now-unused menu.window.fullImage i18n key. * (docs) Sweep for per-step memory, landing role and reset semantics - ux-principles.md: navigation rules 7 (per-step memory) and 8 (landing is orientation, not navigation); reset semantics in rule 3 updated to morph-to-factory for a step; N4/N7 reflect the bar as the single nav source. - workspace-guide.md: per-step layout memory + Reset all layouts; startsida rewritten as a welcome card pointing at the bar. - ROADMAP.md: drop the delivered Arbetsflodes-layoutpresets item; refresh the FileQueue n/p entry (companion foundation exists via the role catalog, the gate itself remains a future PR). - CHANGELOG.md: Added (per-step memory), Changed (landing trim), Removed (dead menu commands + fullImage key).
…Alt (#241) The document-level review keydown handler is active over the module's companion panels (image viewer / original view). Its digit (1-5) and letter (a/i/r/x/m) shortcuts matched on the bare key without checking modifiers, so a Cmd+1..5 menu accelerator was consumed by preventDefault before Electron could fire it (step switch never ran) and also wrongly selected a face alternative. Require no Cmd/Ctrl/Alt for those bare-key branches so modified presses fall through to the accelerator while plain presses still act. Shift+Cmd+A (accept all) keeps its own branch.
…p fix (#242) * (landing) Render welcome card below the WorkflowBar, not over it The StartupLanding welcome card was a full-viewport overlay (position: fixed; inset: 0; z-index: 1000, left over from before the persistent WorkflowBar existed), so on a normal start it painted over the bar and only the card was visible. Move the card inside the layout host (.workspace-layout-host, the area below the bar) and switch its CSS to an absolute fill with z-index: var(--z-sticky) — above module content, below modals. The bar now stays visible and clickable while the card is up; ShortcutsHelp and confirm dialogs still paint above it as higher-z siblings of the shell. * (welcome-first-run) Show the welcome card only on the first session The welcome card appeared on every non-CLI start. It is now first-run only: a persistent localStorage flag (ansikten-welcomed, welcomeFlag.js) is set the first time the card is dismissed — opening a step, loading an image, or closing it from the menu — routed through a single dismissLanding() helper. Later launches drop straight into the workspace with the WorkflowBar and no card; an emptied workspace no longer brings the card back for a returning user. The flag fails open toward showing (missing/corrupt counts as not yet welcomed), and CLI launches (willLaunch) still never show it. A new Help ▸ "Visa välkomstguiden" menu item (show-welcome) re-shows it on demand, wired through the menu-command table. Tests: welcomeFlag unit tests, plus workspace tests for visibility structure, first-run set/skip, corrupt fail-open, CLI suppression, and the show-welcome re-show. * (welcome-first-run) Keep the empty-workspace card as an unconditional fallback The first-run flag should govern only the at-start card shown over the non-empty default layout. Closing every view is a separate trigger: the empty model must always bring the card back so the workspace is never a dead end, regardless of whether the user has been welcomed. Drop the !hasBeenWelcomed() guard on the empty-workspace re-show; the card fills the host under the always-visible WorkflowBar. Add a test for the unconditional fallback and document the two-trigger semantics in ux-principles and the workspace guide. * (roadmap) Note Review arrow-key double-fire (Cmd+Arrow steps face and swaps panel) * (shortcuts) Wire Help ▸ Keyboard Shortcuts to toggle the overlay The show-keyboard-shortcuts menu command (and its Cmd+/ accelerator) had no renderer handler and fell through to a no-op moduleAPI broadcast, so the menu item did nothing — only the `?` key opened the overlay. Add a toggleShortcutsHelp callback to the menu-command context and map show-keyboard-shortcuts to it, matching the `?`-key toggle. The menu item now opens (and closes) the ShortcutsHelp overlay. * (welcome-first-run) Mark welcomed only when the card was actually shown A CLI launch (willLaunch) renders no card, but its launch command opens a step/module that still reaches dismissLanding — which unconditionally set the welcomed flag, silently burning the first-run guide for a CLI-first user on their later normal start. Gate markWelcomed on the card being visible at dismissal: dismissLanding now uses a functional setShowLanding updater to read the live value and marks welcomed only when it was showing. markWelcomed stays idempotent so a StrictMode double-invoke is harmless. Add a test asserting a CLI-launch dismissal leaves the flag unset and a later normal start still shows the card; normal dismissal still sets the flag.
…er in Review (#243) * (workspace-morph) Support tab groups in step specs, morph and memory Extend the pane-spec format so a step column can be a tab GROUP ({ tabs, active, weight }) rather than only a single module, and teach the morph engine to build/diff grouped columns non-destructively (preserved node ids, idempotent fast path, keepMounted tabs pinned to enableRenderOnDemand:false so a hidden companion stays mounted). Per-step memory snapshots and restores grouped tabs incl. the active tab, and a one-time migrateReviewMemoryShape folds an old three-column review memory into the new companion-tab form. * (workspace) Review file queue as companion tab: migrate memory + surface viewer on load Wire migrateReviewMemoryShape into workspace init so existing installs adopt the new review default, and surface the Image Viewer tab on image-loaded when it is hidden behind the File Queue companion tab (programmatic selectTab, no DOM-focus steal) so a queue-driven load is always shown. * (FileQueueModule) Gate n/p on mounted-not-parked so navigation survives being the hidden companion tab The queue's n/p (next/previous file) were gated on tab visibility, which breaks the owner's primary review gesture the moment the Image Viewer tab is raised over the hidden File Queue companion. Gate n/p on mounted-and-not-parked (parent is a tabset, not a background border) instead; a queue parked under a different step stays inert. Focus-dependent keys (Cmd+F, Cmd+A, /) remain visibility-gated. * (test) Cover review companion-tab shape: tab groups, hidden-mount, n/p gate, memory migration Update the review-shape assertions to the new review-module | [image-viewer, file-queue] form, and add coverage for the pane-shape helpers, grouped-column build/idempotence, the hidden File-Queue staying mounted (React mount level), the mounted-not-parked n/p gate, grouped-tab snapshot/restore and migrateReviewMemoryShape. * (docs) Document review companion-tab layout, tab-group support and companion invariant CHANGELOG Changed entry; ROADMAP marks the n/p companion-gate item done; ux-principles gains navigation rule 9 (companion drivers stay mounted, keys gate on mounted-not-parked, surface the partner on load) and a grouped-tabs memory note; workspace-guide describes the new Granska layout, Cmd+Shift+U and n/p. * (test) Cover image-loaded surfacing of the hidden Image Viewer tab Extract the inline image-loaded effect body into a behavior-neutral pure helper revealHiddenModuleTab(model, moduleId) in workspaceMorph.js (selects a module's tab only when it is hidden behind another tab in a real tabset) and call it from the workspace effect, so the behavior is unit-testable. Add two rendered-Layout tests: (a) viewer hidden behind the File Queue tab is surfaced on image-loaded; (b) an already-visible viewer is a no-op (no extra selectTab).
…hosphor (#244) * (workflowbar-autohide) Autohide the workflow bar with retro slide + phosphor Default-on autohide: the workflow bar slides out of flow and overlays the top of the layout host (Lightroom module-row style), so the workspace reclaims the height and FlexLayout never re-layouts as it shows/hides. It returns on a step change, a top-edge hover-zone, or keyboard focus; an open dropdown, focus-within, or the pointer resting on it pause the hide timer. Timer logic lives in a headless hook (useWorkflowBarAutoHide); the slide and accent phosphor pulse are CSS-only using existing theme tokens and are neutralised under prefers-reduced-motion. A new independent preference (workspace.workflowBarAutoHide, default true) offers the always-visible opt-out, separate from showWorkflowBar. * (workflowbar-autohide) Lift the autohide overlay above the welcome card The autohide bar and its hover-zone overlay the layout host at z 6-7, but the StartupLanding welcome card fills that host at --z-sticky (200) with default pointer-events, so on the first-run / empty workspace it painted over the row and swallowed its clicks — leaving a mouse user no way to reach the bar. Raise the overlay + hover-zone to --z-sticky + 100 (still below --z-modal so modals win). Update the StartupLanding comment to describe the new ordering. * (workflowbar-autohide) Keep the hidden bar out of the tab order and reveal on focus The hidden row was translateY(-100%) + aria-hidden, but its buttons stayed in the tab order (focusable-inside-aria-hidden antipattern) and onFocus only paused the timer without revealing. Replace aria-hidden with `inert` while hidden — the whole row leaves the tab order and a11y tree and cannot take focus or clicks; the catch-strip and Cmd+1..5 step changes remain the ways back in. onFocus now also reveals, so a Tab into a still-visible, just-idle row surfaces it before it slides away. Tests cover both: inert toggles with hidden state and the hover-zone restores it, and focus reveals + pauses the hide timer.
…autohide gate (#245) * (WorkflowBar) Float the revealed autohide bar as an inset pill In autohide mode the revealed row now detaches from the edges — a horizontal inset + small top gap (--space tokens), rounded corners (--radius-lg), an elevated surface (--bg-elevated), a drop shadow (--shadow-lg) and a resting accent phosphor glow — so it reads as a floating capsule rather than a docked edge-to-edge strip. The phosphor keyframe settles onto the resting glow (no snap), and the hidden transform clears the top gap while dropping the shadow so nothing peeks past the top edge. The in-flow bar (autohide off) is unchanged. No new theme keys; light + dark. * (workflowbar-autohide) Suspend autohide on the welcome / empty workspace The bar could slide away while only the welcome card (or an empty workspace) was showing — hiding navigation with nothing behind it to uncover, and leaving a mouse user no way in on first run. useAutoHide now takes a hasContent flag that gates it exactly like the enabled opt-out: false keeps the bar revealed with no timer, and a false→true flip (a view opens) starts it. FlexLayoutWorkspace derives hasContent as !showLanding && tabCount > 0 (tabCount tracked alongside the existing empty-workspace fallback) and passes it through. Adds hook tests for the gate and updates the docs (this also subsumes the Nagelfar first-run reachability residual).
…246) * (roadmap) Log inherited micro-edge + navigation-series cleanup * (docs) Document workspace router + workflow morph after nav overhaul * (release) Prepare v1.8.0: changelog + version bump
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release v1.8.0. Se CHANGELOG.md.