mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
feat(settings): tighten the surface, put Add Case on it, retire the tab chrome
Three things, all on the same surface. **Tighter.** The shell drops to 760x620 (was 840x700) and the density comes down with it: rail 176px, doc padding 15px, row padding 5px 10px, group gaps 3px, section head 0.88rem, row label 0.76rem, description 0.645rem. The model cards were the biggest block in the document and shrink the most (6px 8px padding, 0.72rem name). The toggle switches keep their size on purpose - only the space around them was the problem. **Checkboxes stay checkboxes.** The respawn cycle steps go back to real checkboxes in a row card (`.set-checks` / `.set-check`) rather than the chips they briefly became: they are numbered steps of one sequence, not a set of independent tags, and chips read as the latter. **Add Case joins the surface.** Same shell, rail and sections; its rail switches panels like Session Options'. The six panels keep their legacy `.form-row` markup - every id in them is read back by session-ui.js, so restructuring the forms would be a lot of risk for no visual gain. Instead an adapter block scoped to `#createCaseModal .set-doc` maps the old primitives onto the look: a form row paints as a row card, its label as a row label, its `.form-hint` as a row description, `<details class="advanced-options">` as a collapsed group head. `.form-row` everywhere else is untouched. With that, `.modal-tabs` / `.modal-tab-btn` / `.modal-tab-content` have no users left, so their CSS is deleted from both stylesheets and the guard in test/app-settings-structure.test.ts flips from "the settings modal must not steal these shared classes" to "nothing uses them any more" - a reappearance now means a modal drifted back off the shared surface.
This commit is contained in:
@@ -264,7 +264,7 @@ Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. L
|
||||
|
||||
**Per-device vs synced settings**: the `displayKeys` set in settings-ui.js is a **client-side merge policy**, not a wire filter. A display key seeds from the server only when localStorage has no value for it, which is what prevents one device overwriting another; `showPlanUsageLimits` is additionally `delete`d from the incoming payload outright. Separately, `SettingsUpdateSchema` is `.strict()` and simply **does not declare** `skin`, `showFileViewerButton`, `showCronButton`, `webglRendererEnabled`, `localEchoEnabled`, `cjkInputEnabled`, or `extendedKeyboardBar`, so sending one of those is a validation error. The rest (`showResponseViewer`, `showPlanUsageLimits`, `language`, and most `show*` keys) ARE in the schema and do persist server-side; they are per-device by client policy only. ⚠️ Adding a new per-device setting means deciding **both** questions: membership in `displayKeys`, and presence in the schema.
|
||||
|
||||
**Settings surface** (`#appSettingsModal` + `#sessionOptionsModal`): the `set-*` language (left rail, groups of rows, control pinned right) is shared by BOTH modals through one `:is(#appSettingsModal, #sessionOptionsModal)` scope in styles.css — an `:is()` list takes its most specific argument's specificity, so both stay at the old id weight. **App Settings** is a left rail that is a **table of contents over ONE scrolling document**, not a tab switcher: every section stays mounted (`.set-section`, ids `settings-system|terminal|layout|appearance|models|clis|notifications|voice|shortcuts`, in that order — System first so the version and the updater are the first thing seen), the rail follows the scroll, and `switchSettingsTab(id)` keeps its historical name but SCROLLS instead of hiding. **Session Options** uses the same surface with a rail that really SWITCHES (`switchOptionsTab` shows one `.set-section` and puts `.hidden` on the rest, since Summary owns its own scroller and Respawn is long); phones give it a horizontal rail strip instead of the jump pill, which it does not have. `test/session-options-structure.test.ts` guards its rail↔section pairing and the `data-claude-only` entries external CLIs drop. Phones swap the rail for the sticky `#appSettingsJump` pill (compact layout at ≤860px in mobile.css). ⚠️ **The load/save contract is `getElementById` by id**: `openAppSettings()`/`saveAppSettings()` read every control by a fixed id, so moving a control between sections is free but renaming or dropping one silently stops it loading or saving. `test/app-settings-structure.test.ts` is the static guard. ⚠️ Model cards (`#appSettingsModelCards`) and the effort segment are **views over hidden `<select>`s** that remain the source of truth; the cards hold the BASE model and the "1M context window" switch composes `base + [1m]` back into `claudeModel`, which is what retires the old "takes precedence over the toggle below" trap. ⚠️ `.modal-tabs`/`.modal-tab-btn`/`.modal-tab-content` now belong to `#createCaseModal` alone; the settings surface uses its own `set-*` classes and must not restyle them. ⚠️ The **Header & Panels live preview** is a scale model rebuilt from the chips (`_syncLayoutPreview`); it owns NO icons, it CLONES `.set-chip-ico` out of the chip, so each icon has exactly one copy in index.html. A chip joins it via `data-preview` (slot) + `data-preview-order`, or `data-preview-text` for readouts that are not buttons. Its frame is painted from skin tokens only (hardcoded black alphas turned it into a grey slab on the light skins) and is `data-i18n-skip`. ⚠️ `admin-ui.js` injects the multi-user Users entry into `.set-rail-items` + `.set-doc`, so those hooks must survive any restructure.
|
||||
**Settings surface** (`#appSettingsModal` + `#sessionOptionsModal` + `#createCaseModal`): the `set-*` language (left rail, groups of rows, control pinned right) is shared by all three modals through one `:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal)` scope in styles.css — an `:is()` list takes its most specific argument's specificity, so both stay at the old id weight. **App Settings** is a left rail that is a **table of contents over ONE scrolling document**, not a tab switcher: every section stays mounted (`.set-section`, ids `settings-system|terminal|layout|appearance|models|clis|notifications|voice|shortcuts`, in that order — System first so the version and the updater are the first thing seen), the rail follows the scroll, and `switchSettingsTab(id)` keeps its historical name but SCROLLS instead of hiding. **Session Options** and **Add Case** use the same surface with a rail that really SWITCHES (`switchOptionsTab` / `switchCaseModalTab` show one `.set-section` and put `.hidden` on the rest, since Summary owns its own scroller, Respawn is long, and Add Case is six independent forms); phones give both a horizontal rail strip instead of the jump pill, which neither has. Add Case keeps its legacy `.form-row` markup (six tabs of it, every id read back by session-ui.js) and is mapped onto the look by an adapter block scoped to `#createCaseModal .set-doc` — do not restructure those forms just to reach the row classes. `test/session-options-structure.test.ts` guards its rail↔section pairing and the `data-claude-only` entries external CLIs drop. Phones swap the rail for the sticky `#appSettingsJump` pill (compact layout at ≤860px in mobile.css). ⚠️ **The load/save contract is `getElementById` by id**: `openAppSettings()`/`saveAppSettings()` read every control by a fixed id, so moving a control between sections is free but renaming or dropping one silently stops it loading or saving. `test/app-settings-structure.test.ts` is the static guard. ⚠️ Model cards (`#appSettingsModelCards`) and the effort segment are **views over hidden `<select>`s** that remain the source of truth; the cards hold the BASE model and the "1M context window" switch composes `base + [1m]` back into `claudeModel`, which is what retires the old "takes precedence over the toggle below" trap. ⚠️ `.modal-tabs`/`.modal-tab-btn`/`.modal-tab-content` are RETIRED — no modal uses them and their CSS is deleted; a reappearance means a modal drifted off the shared surface (pinned by `test/app-settings-structure.test.ts`). ⚠️ The **Header & Panels live preview** is a scale model rebuilt from the chips (`_syncLayoutPreview`); it owns NO icons, it CLONES `.set-chip-ico` out of the chip, so each icon has exactly one copy in index.html. A chip joins it via `data-preview` (slot) + `data-preview-order`, or `data-preview-text` for readouts that are not buttons. Its frame is painted from skin tokens only (hardcoded black alphas turned it into a grey slab on the light skins) and is `data-i18n-skip`. ⚠️ `admin-ui.js` injects the multi-user Users entry into `.set-rail-items` + `.set-doc`, so those hooks must survive any restructure.
|
||||
|
||||
**Header button visibility**: most header controls are opt-in and hidden by a marker class (`btn-multimonitor--hidden`, `btn-response-viewer-header--hidden`, `btn-file-viewer--hidden`, `btn-cron--hidden`) that `applyHeaderVisibilitySettings()` (settings-ui.js) toggles after settings load; the multi-monitor button is instead stripped at render by `renderIndexHtml`. ⚠️ Hiding must go through the marker class: the base rules are `display:inline-flex !important`, so an inline style cannot override them. Current desktop default is WS/CPU/MEM + File Viewer + gear, with the token chip and lifecycle-log button OFF. ⚠️ New header controls must not leak onto phones; `test/mobile-header-buttons-policy.test.ts` is the static guard. → [architecture-invariants#header-button-visibility-multi-monitor-response-viewer-file-viewer-cron](docs/architecture-invariants.md#header-button-visibility-multi-monitor-response-viewer-file-viewer-cron)
|
||||
|
||||
|
||||
+83
-23
@@ -1104,10 +1104,12 @@
|
||||
<textarea id="modalRespawnPrompt" class="set-textarea" rows="1" placeholder="Prompt to send when idle" onchange="app.autoSaveRespawnConfig()">update all the docs and CLAUDE.md</textarea>
|
||||
</div>
|
||||
|
||||
<div class="set-chips" id="respawnStepChips">
|
||||
<label class="set-chip"><input type="checkbox" id="modalRespawnSendClear" checked onchange="app.autoSaveRespawnConfig()"><span>2. Send /clear</span></label>
|
||||
<label class="set-chip"><input type="checkbox" id="modalRespawnSendInit" checked onchange="app.autoSaveRespawnConfig()"><span>3. Send /init</span></label>
|
||||
<label class="set-chip" title="Presses Enter for plan approvals and default question options"><input type="checkbox" id="modalRespawnAutoAccept" checked onchange="app.autoSaveRespawnConfig()"><span>Auto-accept prompts</span></label>
|
||||
<!-- Real checkboxes, deliberately: these are numbered steps of
|
||||
one cycle, not a set of independent tags. -->
|
||||
<div class="set-row set-checks">
|
||||
<label class="set-check"><input type="checkbox" id="modalRespawnSendClear" checked onchange="app.autoSaveRespawnConfig()"><span>2. Send /clear</span></label>
|
||||
<label class="set-check"><input type="checkbox" id="modalRespawnSendInit" checked onchange="app.autoSaveRespawnConfig()"><span>3. Send /init</span></label>
|
||||
<label class="set-check" title="Presses Enter for plan approvals and default question options"><input type="checkbox" id="modalRespawnAutoAccept" checked onchange="app.autoSaveRespawnConfig()"><span>Auto-accept prompts</span></label>
|
||||
</div>
|
||||
|
||||
<div class="set-row set-row-block">
|
||||
@@ -2285,24 +2287,56 @@
|
||||
</div>
|
||||
|
||||
<!-- Create Case Modal -->
|
||||
<!-- Add Case: same `set-*` surface as App Settings and Session Options; the
|
||||
rail switches panels (switchCaseModalTab in session-ui.js). -->
|
||||
<div class="modal" id="createCaseModal">
|
||||
<div class="modal-backdrop" onclick="app.closeCreateCaseModal()"></div>
|
||||
<div class="modal-content modal-lg">
|
||||
<div class="modal-header">
|
||||
<div class="modal-content modal-lg set-shell">
|
||||
<div class="modal-header set-shell-head">
|
||||
<h3>Add Case</h3>
|
||||
<button class="modal-close" onclick="app.closeCreateCaseModal()" aria-label="Close create case">×</button>
|
||||
<div class="set-head-actions">
|
||||
<button class="modal-close" onclick="app.closeCreateCaseModal()" aria-label="Close create case">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-tabs">
|
||||
<button class="modal-tab-btn active" data-tab="case-create">Create New</button>
|
||||
<button class="modal-tab-btn" data-tab="case-clone" id="caseCloneTabBtn">Clone Repo</button>
|
||||
<button class="modal-tab-btn" data-tab="case-link">Link Existing</button>
|
||||
<button class="modal-tab-btn" data-tab="case-remote">Remote</button>
|
||||
<button class="modal-tab-btn" data-tab="case-docker">Docker</button>
|
||||
<button class="modal-tab-btn" data-tab="case-manage">Manage</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="set-body">
|
||||
<nav class="set-rail" id="createCaseRail" aria-label="Add case sections">
|
||||
<div class="set-rail-items">
|
||||
<button type="button" class="set-rail-item active" data-tab="case-create">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M12 8v8M8 12h8"/></svg>
|
||||
<span>Create New</span>
|
||||
</button>
|
||||
<button type="button" class="set-rail-item" data-tab="case-clone" id="caseCloneTabBtn">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>
|
||||
<span>Clone Repo</span>
|
||||
</button>
|
||||
<button type="button" class="set-rail-item" data-tab="case-link">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
|
||||
<span>Link Existing</span>
|
||||
</button>
|
||||
<button type="button" class="set-rail-item" data-tab="case-remote">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3a15 15 0 0 1 0 18a15 15 0 0 1 0-18z"/></svg>
|
||||
<span>Remote</span>
|
||||
</button>
|
||||
<button type="button" class="set-rail-item" data-tab="case-docker">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12h18v4a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3z"/><rect x="6" y="7" width="4" height="4"/><rect x="11" y="7" width="4" height="4"/><rect x="11" y="2.5" width="4" height="4"/></svg>
|
||||
<span>Docker</span>
|
||||
</button>
|
||||
<button type="button" class="set-rail-item" data-tab="case-manage">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/><circle cx="9" cy="6" r="2"/><circle cx="15" cy="12" r="2"/><circle cx="8" cy="18" r="2"/></svg>
|
||||
<span>Manage</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="set-rail-foot">A case is a workspace: a directory, its CLAUDE.md, and where its sessions run.</p>
|
||||
</nav>
|
||||
|
||||
<div class="set-doc" id="createCaseDoc">
|
||||
<!-- Create New Tab -->
|
||||
<div class="modal-tab-content" id="case-create">
|
||||
<div class="set-section" id="case-create">
|
||||
<div class="set-section-head">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M12 8v8M8 12h8"/></svg>
|
||||
<h2>Create New</h2>
|
||||
</div>
|
||||
<p class="set-section-blurb">A fresh workspace under ~/codeman-cases, scaffolded with its own CLAUDE.md.</p>
|
||||
<div class="form-row">
|
||||
<label>Case Name</label>
|
||||
<input type="text" id="newCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||||
@@ -2361,7 +2395,12 @@
|
||||
</details>
|
||||
</div>
|
||||
<!-- Clone Repo Tab (issue #236) -->
|
||||
<div class="modal-tab-content hidden" id="case-clone">
|
||||
<div class="set-section hidden" id="case-clone">
|
||||
<div class="set-section-head">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>
|
||||
<h2>Clone Repo</h2>
|
||||
</div>
|
||||
<p class="set-section-blurb">Clone a public repository into a new case. Anonymous HTTPS or your own credential helper.</p>
|
||||
<div class="form-row">
|
||||
<label>Repository URL</label>
|
||||
<input type="url" id="cloneRepoUrl" placeholder="https://github.com/owner/repo.git" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" oninput="app.onCloneUrlInput()">
|
||||
@@ -2406,7 +2445,12 @@
|
||||
<span class="form-hint" id="cloneCaseNote" style="margin-top: 8px; display: block;">The clone runs while this request is open, so a large repository takes a while. The case appears as soon as git finishes, even if the browser gave up waiting.</span>
|
||||
</div>
|
||||
<!-- Link Existing Tab -->
|
||||
<div class="modal-tab-content hidden" id="case-link">
|
||||
<div class="set-section hidden" id="case-link">
|
||||
<div class="set-section-head">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
|
||||
<h2>Link Existing</h2>
|
||||
</div>
|
||||
<p class="set-section-blurb">Point a case at a folder that already exists on this machine.</p>
|
||||
<div class="form-row">
|
||||
<label>Case Name</label>
|
||||
<input type="text" id="linkCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||||
@@ -2422,7 +2466,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- Remote Tab -->
|
||||
<div class="modal-tab-content hidden" id="case-remote">
|
||||
<div class="set-section hidden" id="case-remote">
|
||||
<div class="set-section-head">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3a15 15 0 0 1 0 18a15 15 0 0 1 0-18z"/></svg>
|
||||
<h2>Remote</h2>
|
||||
</div>
|
||||
<p class="set-section-blurb">Run the agent on another machine over SSH, from a tab here.</p>
|
||||
<div class="form-row">
|
||||
<label>Case Name</label>
|
||||
<input type="text" id="remoteCaseName" placeholder="gpu-work" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||||
@@ -2493,7 +2542,12 @@
|
||||
</details>
|
||||
</div>
|
||||
<!-- Docker Tab -->
|
||||
<div class="modal-tab-content hidden" id="case-docker">
|
||||
<div class="set-section hidden" id="case-docker">
|
||||
<div class="set-section-head">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12h18v4a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3z"/><rect x="6" y="7" width="4" height="4"/><rect x="11" y="7" width="4" height="4"/><rect x="11" y="2.5" width="4" height="4"/></svg>
|
||||
<h2>Docker</h2>
|
||||
</div>
|
||||
<p class="set-section-blurb">Run the case inside a container: one per case, shared by all its sessions.</p>
|
||||
<div class="form-row">
|
||||
<label>Case Name</label>
|
||||
<input type="text" id="dockerCaseName" placeholder="sandbox" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||||
@@ -2546,7 +2600,12 @@
|
||||
<span class="form-hint" id="dockerLinkStatus" style="margin-top: 8px; display: block;"></span>
|
||||
</div>
|
||||
<!-- Manage Tab -->
|
||||
<div class="modal-tab-content hidden" id="case-manage">
|
||||
<div class="set-section hidden" id="case-manage">
|
||||
<div class="set-section-head">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/><circle cx="9" cy="6" r="2"/><circle cx="15" cy="12" r="2"/><circle cx="8" cy="18" r="2"/></svg>
|
||||
<h2>Manage</h2>
|
||||
</div>
|
||||
<p class="set-section-blurb">Reorder or remove cases, and pick up anything exported from a docker case.</p>
|
||||
<div class="case-manage-list" id="caseManageList">
|
||||
<!-- Populated by JS -->
|
||||
</div>
|
||||
@@ -2559,8 +2618,9 @@
|
||||
<div class="case-manage-list" id="dockerExportsList"><span class="form-hint">No exports yet. Export a docker case from its tab.</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<div class="form-actions set-foot">
|
||||
<button class="btn-toolbar" onclick="app.closeCreateCaseModal()">Cancel</button>
|
||||
<button class="btn-toolbar btn-primary" id="caseModalSubmit" onclick="app.submitCaseModal()">Create</button>
|
||||
</div>
|
||||
|
||||
+67
-122
@@ -223,24 +223,6 @@ html.mobile-init .file-browser-panel {
|
||||
min-height: 56px;
|
||||
}
|
||||
|
||||
.modal-tabs {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.modal-tabs::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modal-tab-btn {
|
||||
padding: 0.4rem 0.75rem;
|
||||
font-size: 0.7rem;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Settings grid stays 2-col on tablet but tighter */
|
||||
.settings-grid {
|
||||
gap: 0.4rem 0.75rem;
|
||||
@@ -2077,45 +2059,8 @@ html.mobile-init .file-browser-panel {
|
||||
|
||||
/* ---- Settings Modal: Mobile Optimizations ---- */
|
||||
|
||||
/* Scrollable tabs row - prevent overflow on small screens */
|
||||
.modal-tabs {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
gap: 0.25rem;
|
||||
padding: 0 0.75rem 0.5rem 0.75rem;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.modal-tabs::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modal-tab-btn {
|
||||
padding: 0.35rem 0.6rem;
|
||||
font-size: 0.65rem;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ---- Case Modal: Mobile Touch Optimizations ---- */
|
||||
|
||||
/* Larger tab buttons for case modal - easy to tap */
|
||||
#createCaseModal .modal-tabs {
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem 0.75rem;
|
||||
}
|
||||
|
||||
#createCaseModal .modal-tab-btn {
|
||||
flex: 1;
|
||||
min-height: 44px;
|
||||
padding: 0.6rem 1rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
border-radius: 8px;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Touch-friendly form inputs in case modal */
|
||||
#createCaseModal .form-row {
|
||||
@@ -3170,21 +3115,21 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
|
||||
stack of separate cards: at 390px the per-card borders were most of the pixels.
|
||||
============================================================================ */
|
||||
@media (max-width: 860px) {
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .modal-content.modal-lg {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .modal-content.modal-lg {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-body {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* Rail keeps only its search field, laid out as a bar */
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-rail {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-rail {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-right: 0;
|
||||
@@ -3195,37 +3140,37 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-rail-items,
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-rail-foot {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-rail-items,
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-rail-foot {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-search {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-search {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-search input {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-search input {
|
||||
padding: 9px 10px 9px 30px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Save moves into the header; the bottom action bar would cost 60px */
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-head-save {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-head-save {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-foot {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-foot {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-doc {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-doc {
|
||||
padding: 0 14px 34px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ── jump control ──────────────────────────────────────────────────── */
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-jump {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump {
|
||||
display: flex;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
@@ -3246,30 +3191,30 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-jump-ico {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-ico {
|
||||
color: var(--accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-jump-label {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-label {
|
||||
font-weight: 580;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-jump-chev {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-chev {
|
||||
margin-left: auto;
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.18s;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-jump[aria-expanded='true'] .set-jump-chev {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump[aria-expanded='true'] .set-jump-chev {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-jump-veil {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-veil {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@@ -3277,7 +3222,7 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
|
||||
background: rgba(4, 8, 13, 0.62);
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-jump-menu {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
@@ -3299,7 +3244,7 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
|
||||
display: block;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-jump-row {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
@@ -3314,43 +3259,43 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-jump-row svg {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-row svg {
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-jump-row .set-jump-count {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-row .set-jump-count {
|
||||
margin-left: auto;
|
||||
font-size: 0.62rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-jump-row.active {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-row.active {
|
||||
background: rgba(var(--accent-rgb), 0.14);
|
||||
color: var(--text);
|
||||
font-weight: 570;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-jump-row.active svg {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-jump-row.active svg {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* ── sections step down: the jump pill already names the current one ── */
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-section {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-section + .set-section {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section + .set-section {
|
||||
border-top: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-section-head {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section-head {
|
||||
gap: 7px;
|
||||
margin: 18px 0 2px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-section-head svg {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section-head svg {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
@@ -3359,7 +3304,7 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-section-head h2 {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section-head h2 {
|
||||
font-size: 0.6rem;
|
||||
font-weight: 640;
|
||||
letter-spacing: 0.1em;
|
||||
@@ -3367,45 +3312,45 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-section-head::after {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section-head::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, var(--border), transparent);
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-section-blurb {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-section-blurb {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ── live layout preview ───────────────────────────────────────────── */
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-preview {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-preview {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-preview-stage {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-preview-stage {
|
||||
min-height: 62px;
|
||||
}
|
||||
|
||||
/* ── inset grouped list ────────────────────────────────────────────── */
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-group {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-group + .set-group {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group + .set-group {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-group-head {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-head {
|
||||
margin-bottom: 7px;
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-group-hint {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-hint {
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-group-body {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body {
|
||||
gap: 0;
|
||||
background: rgba(255, 255, 255, 0.035);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
@@ -3413,7 +3358,7 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-group-body > .set-row {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > .set-row {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
@@ -3421,103 +3366,103 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-group-body > .set-row + .set-row {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > .set-row + .set-row {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.055);
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-group-body > .set-chips,
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-group-body > .set-modelgrid,
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-group-body > .set-minigrid,
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-group-body > #appSettingsShortcutsList {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > .set-chips,
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > .set-modelgrid,
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > .set-minigrid,
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > #appSettingsShortcutsList {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-group-body > .event-type-grid {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-group-body > .event-type-grid {
|
||||
padding: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-row-label {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row-label {
|
||||
font-size: 0.84rem;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-row-desc {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row-desc {
|
||||
font-size: 0.69rem;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
/* Fields go full width under their label instead of fighting for the row */
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-row.has-field {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row.has-field {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 9px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-row.has-field .set-select,
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-row.has-field .set-input {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row.has-field .set-select,
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row.has-field .set-input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-row-actions-wide {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row-actions-wide {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-row-actions-wide .set-input {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row-actions-wide .set-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-num {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-num {
|
||||
width: 76px;
|
||||
}
|
||||
|
||||
/* Bigger touch targets for the toggles and chips */
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .switch-sm {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .switch-sm {
|
||||
width: 40px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .switch-sm .slider:before {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .switch-sm .slider:before {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .switch-sm input:checked + .slider:before {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .switch-sm input:checked + .slider:before {
|
||||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-chip {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-chip {
|
||||
font-size: 0.78rem;
|
||||
padding: 9px 14px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-modelgrid {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-modelgrid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-minigrid {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-minigrid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-mini .set-select {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-mini .set-select {
|
||||
width: 148px;
|
||||
}
|
||||
|
||||
/* One scrollable line beats a ragged two-row wrap for 7 effort levels */
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-segment {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-segment {
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-segment::-webkit-scrollbar {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-segment::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:is(#appSettingsModal, #sessionOptionsModal) .set-segment button {
|
||||
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-segment button {
|
||||
flex: 0 0 auto;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
@@ -3532,11 +3477,11 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
|
||||
what its tab bar was before the two modals started sharing a surface.
|
||||
============================================================================ */
|
||||
@media (max-width: 860px) {
|
||||
#sessionOptionsModal .set-rail {
|
||||
:is(#sessionOptionsModal, #createCaseModal) .set-rail {
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
#sessionOptionsModal .set-rail-items {
|
||||
:is(#sessionOptionsModal, #createCaseModal) .set-rail-items {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
@@ -3546,22 +3491,22 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
#sessionOptionsModal .set-rail-items::-webkit-scrollbar {
|
||||
:is(#sessionOptionsModal, #createCaseModal) .set-rail-items::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#sessionOptionsModal .set-rail-item {
|
||||
:is(#sessionOptionsModal, #createCaseModal) .set-rail-item {
|
||||
white-space: nowrap;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
/* The active marker is a left bar in the vertical rail; horizontally that
|
||||
reads as a stray tick, so the strip uses a filled pill instead. */
|
||||
#sessionOptionsModal .set-rail-item.active::before {
|
||||
:is(#sessionOptionsModal, #createCaseModal) .set-rail-item.active::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#sessionOptionsModal .set-rail-item.active {
|
||||
:is(#sessionOptionsModal, #createCaseModal) .set-rail-item.active {
|
||||
background: rgba(var(--accent-rgb), 0.13);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1808,7 +1808,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
this.switchCaseModalTab('case-create');
|
||||
// Wire up tab buttons
|
||||
const modal = document.getElementById('createCaseModal');
|
||||
modal.querySelectorAll('.modal-tabs .modal-tab-btn').forEach(btn => {
|
||||
modal.querySelectorAll('.set-rail-item').forEach(btn => {
|
||||
btn.onclick = () => this.switchCaseModalTab(btn.dataset.tab);
|
||||
});
|
||||
// Scroll-into-view on focus for mobile keyboard visibility
|
||||
@@ -1829,14 +1829,17 @@ Object.assign(CodemanApp.prototype, {
|
||||
switchCaseModalTab(tabName) {
|
||||
this.caseModalTab = tabName;
|
||||
const modal = document.getElementById('createCaseModal');
|
||||
// Toggle active class on tab buttons
|
||||
modal.querySelectorAll('.modal-tabs .modal-tab-btn').forEach(btn => {
|
||||
// Toggle active class on rail entries
|
||||
modal.querySelectorAll('.set-rail-item').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.tab === tabName);
|
||||
});
|
||||
// Toggle hidden class on tab content
|
||||
modal.querySelectorAll('.modal-tab-content').forEach(content => {
|
||||
// Toggle hidden class on the panels
|
||||
modal.querySelectorAll('.set-section').forEach(content => {
|
||||
content.classList.toggle('hidden', content.id !== tabName);
|
||||
});
|
||||
// A switched-to panel starts at its own top.
|
||||
const doc = document.getElementById('createCaseDoc');
|
||||
if (doc) doc.scrollTop = 0;
|
||||
// Update submit button (hide for manage tab)
|
||||
const submitBtn = document.getElementById('caseModalSubmit');
|
||||
if (tabName === 'case-manage') {
|
||||
|
||||
+332
-209
File diff suppressed because it is too large
Load Diff
@@ -122,15 +122,15 @@ describe('App Settings modal structure', () => {
|
||||
expect(modal).toContain('id="appSettingsOpusContext1m"');
|
||||
});
|
||||
|
||||
it('never hides sections behind .modal-tab-content (that class means display:none)', () => {
|
||||
it('has retired the modal-tab chrome everywhere, not just here', () => {
|
||||
// Session Options and Add Case moved onto this same `set-*` surface, so the
|
||||
// old tab classes have no users left. A reappearance means a modal drifted
|
||||
// back off the shared surface (or the dead CSS was resurrected).
|
||||
expect(settingsModal()).not.toContain('modal-tab-content');
|
||||
});
|
||||
|
||||
it('leaves the shared modal tab classes to the other modals', () => {
|
||||
// #sessionOptionsModal and #createCaseModal still use .modal-tabs; the
|
||||
// settings rail must not restyle them out from under those.
|
||||
expect(settingsModal()).not.toContain('class="modal-tabs"');
|
||||
expect(html).toContain('<div class="modal-tabs">');
|
||||
expect(html).not.toContain('class="modal-tabs"');
|
||||
expect(html).not.toContain('modal-tab-btn');
|
||||
const css = readFileSync(resolve(publicDir, 'styles.css'), 'utf8');
|
||||
expect(css).not.toContain('.modal-tab-btn {');
|
||||
});
|
||||
|
||||
it('exposes the rail hooks admin-ui.js injects the Users section into', () => {
|
||||
|
||||
@@ -91,7 +91,7 @@ describe('Session Options modal structure', () => {
|
||||
expect(modal).not.toContain('modal-tab-content');
|
||||
// The `set-*` rules are shared by both modals through one :is() scope.
|
||||
const css = readFileSync(resolve(publicDir, 'styles.css'), 'utf8');
|
||||
expect(css).toContain(':is(#appSettingsModal, #sessionOptionsModal) .set-row {');
|
||||
expect(css).toContain('#sessionOptionsModal .set-section.hidden {');
|
||||
expect(css).toContain(':is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-row {');
|
||||
expect(css).toContain(':is(#sessionOptionsModal, #createCaseModal) .set-section.hidden {');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user