From 99e7b018a028977f12bcfeb3525f796f9b575e87 Mon Sep 17 00:00:00 2001 From: An Khang Date: Thu, 8 Oct 2026 11:20:26 +0700 Subject: [PATCH] fix(ui): eliminate button overlapping on PC and mobile viewports with flex gaps --- CHANGELOG.md | 6 +- README.md | 2 +- docs/PROJECT_SOURCE_OF_TRUTH.md | 2 +- src/ui/conflictResolutionModal.ts | 3 +- src/ui/syncDashboardModal.ts | 3 +- styles.css | 139 +++++++++++++++++++++++++--- tests/c5MobileAccessibility.test.ts | 20 ++++ 7 files changed, 156 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adbeae6..d08b995 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to GitHub Vault Relay will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -Current repository evidence is 44 test files and 482 passing tests; `npm run verify` passed locally on 2026-10-08. Test totals in older release entries are historical snapshots from those releases, not current totals. +Current repository evidence is 44 test files and 483 passing tests; `npm run verify` passed locally on 2026-10-08. Test totals in older release entries are historical snapshots from those releases, not current totals. Paths and implementation details in older entries are historical release snapshots; the current internal path is `${app.vault.configDir}/github-vault-relay/`. --- @@ -15,12 +15,12 @@ Paths and implementation details in older entries are historical release snapsho ### UI Modernization & Architecture Consolidation - **Unified Modal Architectures**: Consolidated duplicate Pull/Push modal pairs into parameter-driven `SyncConfirmModal` and `SyncResultModal`. Pruned 4 redundant legacy modal files (`pullConfirmModal.ts`, `pushConfirmModal.ts`, `pullResultModal.ts`, `pushResultModal.ts`), eliminating over 930 lines of boilerplate while maintaining 100% feature and telemetry parity. - **Embedded Dashboard File Inspection**: Integrated interactive, expandable change inspection directly within `SyncDashboardModal`. Vault changes can now be filtered by category (*All, Local Only, Remote Only, Local Changed, Remote Changed, Conflicts, Deletes*) and inspected without opening separate stacking modal dialogs on mobile devices. -- **Mobile-First Stylesheet & Responsive Touch UX**: Eliminated all inline style string attributes across UI components in favor of clean semantic classes in `styles.css`. Modernized action buttons for small viewports (`@media (max-width: 480px)`) into full-width stacked layouts, preventing button line-breaks while guaranteeing minimum 44px tap targets and `safe-area-inset-bottom` padding. +- **Mobile-First Stylesheet & Responsive Touch UX**: Eliminated all inline style string attributes across UI components in favor of clean semantic classes in `styles.css`. Modernized action buttons with flex layouts, explicit gaps, and inline-flex vertical centering, preventing button overlap across both PC and mobile viewports. On small viewports (`@media (max-width: 480px)`), buttons adapt into full-width stacked layouts and two-column filter chips while guaranteeing minimum 44px tap targets and `safe-area-inset-bottom` padding. - **SettingsTab Declarative Consolidation**: Replaced duplicate imperative DOM creation in `VaultRelaySettingTab.display()` with an iterator over `getSettingDefinitions()`, cutting ~250 lines of duplicate settings logic and strongly typing setting definitions with 0 ESLint warnings. ### Verification & Test Suite Hardening - **Empirical Challenger & Stress Suites**: Added comprehensive adversarial stress suites (`tests/empiricalChallengerM1.test.ts` and `tests/m1ChallengerStress.test.ts`) covering 39 edge-case, reentrancy, and execution lifecycle scenarios. -- `npm run verify`: PASS — 44 test files, 482 tests passing, 0 lint warnings, clean typecheck, clean production build. +- `npm run verify`: PASS — 44 test files, 483 tests passing, 0 lint warnings, clean typecheck, clean production build. - `verify-release.mjs`: PASS — All manifests, package, lockfile, and documentation files strictly synchronized. ## [1.1.0] - 2026-10-07 diff --git a/README.md b/README.md index 4e893af..5351f75 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - Release: `1.2.0` (manifest, package, tag, and `main` agree after release). - Minimum Obsidian version: `1.11.4`. - Mobile support: enabled (`isDesktopOnly: false`). -- Automated evidence: 44 Vitest test files and 482 passing tests in the current tree. +- Automated evidence: 44 Vitest test files and 483 passing tests in the current tree. - Latest local quality gate: `npm run verify` PASS on 2026-10-08 (lint, typecheck, test, build). - The production bundle is generated by `npm run build`; `npm run verify` is the canonical gate. - Release-gate physical acceptance is PASS on Windows and iPhone for settings, hidden paths, normal files, and restart behavior. See [the manual matrix](docs/MANUAL_TEST_MATRIX.md) for the recorded sign-off. diff --git a/docs/PROJECT_SOURCE_OF_TRUTH.md b/docs/PROJECT_SOURCE_OF_TRUTH.md index 7a1c1ce..6c66472 100644 --- a/docs/PROJECT_SOURCE_OF_TRUTH.md +++ b/docs/PROJECT_SOURCE_OF_TRUTH.md @@ -12,7 +12,7 @@ This is the current factual reference for Vault Relay. When this document confli | Mobile | Supported; `isDesktopOnly: false` | `manifest.json`, `tests/manifest.test.ts` | | Remote service | GitHub REST/Git Data API over HTTPS | `src/github/githubClient.ts` | | Test files | `44` | `tests/*.test.ts`, current `vitest run` output | -| Passing tests | `482` | current `vitest run` output | +| Passing tests | `483` | current `vitest run` output | | Quality gate | `PASS` (`npm run verify`, 2026-10-08) | `package.json`, local gate run | The stable 1.2.0 release is published at `https://github.com/ankhang0704/github-vault-relay/releases/tag/1.2.0`. Provenance is generated by `.github/workflows/release.yml` from immutable tag `1.2.0`. diff --git a/src/ui/conflictResolutionModal.ts b/src/ui/conflictResolutionModal.ts index c198956..8715e26 100644 --- a/src/ui/conflictResolutionModal.ts +++ b/src/ui/conflictResolutionModal.ts @@ -96,7 +96,8 @@ export class ConflictResolutionModal extends Modal { text: "No active conflicts detected. All files are synchronized or safe.", cls: "vault-relay-empty-state-text", }); - const closeBtn = contentEl.createEl("button", { text: "Close", cls: "mod-cta vault-relay-btn-lg" }); + const actions = contentEl.createDiv({ cls: "vault-relay-action-row" }); + const closeBtn = actions.createEl("button", { text: "Close", cls: "mod-cta vault-relay-btn-lg" }); closeBtn.onclick = () => this.close(); return; } diff --git a/src/ui/syncDashboardModal.ts b/src/ui/syncDashboardModal.ts index 2dd73e4..04a8f55 100644 --- a/src/ui/syncDashboardModal.ts +++ b/src/ui/syncDashboardModal.ts @@ -95,7 +95,8 @@ export class SyncDashboardModal extends Modal { cls: "vault-relay-notice-box", }); - const btn = contentEl.createEl("button", { text: "Close", cls: "mod-cta vault-relay-btn-lg" }); + const actions = contentEl.createDiv({ cls: "vault-relay-action-row" }); + const btn = actions.createEl("button", { text: "Close", cls: "mod-cta vault-relay-btn-lg" }); btn.onclick = () => this.close(); } diff --git a/styles.css b/styles.css index 42042db..4aa3d0f 100644 --- a/styles.css +++ b/styles.css @@ -64,14 +64,21 @@ .vault-relay-settings button { min-height: 44px; min-width: 44px; - padding: 8px 12px; - white-space: normal; + padding: 8px 14px; + white-space: nowrap; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + margin: 0; + cursor: pointer; transition: opacity 0.15s ease, background-color 0.15s ease; } .vault-relay-btn-lg { min-height: 44px; padding: 8px 16px; + font-size: 0.92em; } .vault-relay-token-input { @@ -112,13 +119,62 @@ opacity: 0.55; } -.vault-relay-action-row, -.vault-relay-actions-row, +.vault-relay-action-row { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + gap: 10px; + margin-top: 16px; + width: 100%; + max-width: 100%; +} + +.vault-relay-action-row button { + margin: 0; + flex: 0 0 auto; +} + +.vault-relay-actions-row { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 12px; + margin: 12px 0; + width: 100%; + max-width: 100%; +} + .vault-relay-filter-bar { + display: flex; flex-wrap: wrap; + align-items: center; + gap: 8px; + margin: 12px 0; + width: 100%; max-width: 100%; } +.vault-relay-filter-bar button { + min-height: 38px; + min-width: auto; + padding: 6px 12px; + font-size: 0.85em; + border-radius: 6px; + white-space: nowrap; + flex: 0 0 auto; + margin: 0; +} + +.vault-relay-settings .setting-item-control, +.vault-relay-modal .setting-item-control { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + .vault-relay-modal, .vault-relay-modal *, .vault-relay-settings, @@ -401,9 +457,15 @@ display: flex; flex-wrap: wrap; gap: 8px; + align-items: center; margin-top: 10px; } +.vault-relay-conflict-actions button { + margin: 0; + white-space: nowrap; +} + /* --- Stat Badges & Grids --- */ .vault-relay-stat-grid { display: grid; @@ -748,12 +810,24 @@ .vault-relay-filter-tab { font-size: 0.78em; - padding: 4px 8px; + padding: 6px 10px; + min-height: 32px; border-radius: 4px; white-space: nowrap; background: transparent; - border: none; + border: 1px solid transparent; color: var(--text-muted); + cursor: pointer; +} + +.vault-relay-filter-tab:hover { + background-color: var(--background-modifier-hover); + color: var(--text-normal); +} + +.vault-relay-filter-tab.mod-cta { + background-color: var(--interactive-accent); + color: var(--text-on-accent); } .vault-relay-move-title { @@ -866,6 +940,12 @@ gap: 6px; } + .vault-relay-modal button, + .vault-relay-settings button { + white-space: normal; + text-align: center; + } + .vault-relay-modal .setting-item, .vault-relay-settings .setting-item { align-items: stretch; @@ -874,24 +954,44 @@ .vault-relay-modal .setting-item-control, .vault-relay-settings .setting-item-control { - flex-wrap: wrap; + display: flex; + flex-direction: column; + gap: 8px; + align-items: stretch; justify-content: flex-start; width: 100%; + margin-top: 8px; } /* Full-width stacked buttons on narrow touchscreens */ - .vault-relay-action-row, - .vault-relay-actions-row, - .vault-relay-conflict-actions { + .vault-relay-action-row { + display: flex; flex-direction: column-reverse; + gap: 10px; + width: 100%; + margin-top: 14px; + } + + .vault-relay-actions-row { + display: flex; + flex-direction: column; + gap: 10px; + width: 100%; + margin: 12px 0; + } + + .vault-relay-conflict-actions { + display: flex; + flex-direction: column; gap: 8px; width: 100%; + margin-top: 10px; } .vault-relay-action-row button { width: 100%; min-height: 44px; - margin-bottom: 0; + margin: 0; } .vault-relay-actions-row button, @@ -900,7 +1000,22 @@ .vault-relay-settings .setting-item-control button { width: 100%; min-height: 44px; - margin-bottom: 0; + margin: 0; + } + + .vault-relay-filter-bar { + display: flex; + flex-wrap: wrap; + gap: 6px; + width: 100%; + } + + .vault-relay-filter-bar button { + flex: 1 1 calc(50% - 6px); + min-height: 40px; + text-align: center; + justify-content: center; + margin: 0; } .vault-relay-file-row { diff --git a/tests/c5MobileAccessibility.test.ts b/tests/c5MobileAccessibility.test.ts index 1f6a9be..a5cc12b 100644 --- a/tests/c5MobileAccessibility.test.ts +++ b/tests/c5MobileAccessibility.test.ts @@ -77,4 +77,24 @@ describe("C5-MOBILE: mobile-first static and accessibility audit", () => { expect(confirmation).toContain('setButtonText("Clear Token")'); expect(confirmation).toContain("This action cannot be undone"); }); + + it("C5-MOBILE-006: button layouts use flex containers and explicit gaps to prevent overlapping on PC and mobile", () => { + const css = read("styles.css"); + + // Base button structure + expect(css).toMatch(/\.vault-relay-modal button[\s\S]*display:\s*inline-flex/); + expect(css).toMatch(/\.vault-relay-modal button[\s\S]*box-sizing:\s*border-box/); + expect(css).toMatch(/\.vault-relay-modal button[\s\S]*margin:\s*0/); + + // Flex container action rows with explicit gaps + expect(css).toMatch(/\.vault-relay-action-row\s*\{[\s\S]*display:\s*flex;[\s\S]*gap:\s*\d+px;/); + expect(css).toMatch(/\.vault-relay-actions-row\s*\{[\s\S]*display:\s*flex;[\s\S]*gap:\s*\d+px;/); + expect(css).toMatch(/\.vault-relay-filter-bar\s*\{[\s\S]*display:\s*flex;[\s\S]*gap:\s*\d+px;/); + expect(css).toMatch(/\.vault-relay-conflict-actions\s*\{[\s\S]*display:\s*flex;[\s\S]*gap:\s*\d+px;/); + + // Mobile media query has explicit vertical gaps and responsive sizing + expect(css).toMatch(/@media\s*\(max-width:\s*480px\)[\s\S]*\.vault-relay-action-row\s*\{[\s\S]*flex-direction:\s*column-reverse;[\s\S]*gap:\s*\d+px;/); + expect(css).toMatch(/@media\s*\(max-width:\s*480px\)[\s\S]*\.vault-relay-actions-row\s*\{[\s\S]*flex-direction:\s*column;[\s\S]*gap:\s*\d+px;/); + expect(css).toMatch(/@media\s*\(max-width:\s*480px\)[\s\S]*\.vault-relay-filter-bar button\s*\{[\s\S]*calc\(50%/); + }); });