diff --git a/.project-docs/30-worklog/tasks/20260902-dashboard-mobile-share-4e91c7.md b/.project-docs/30-worklog/tasks/20260902-dashboard-mobile-share-4e91c7.md new file mode 100644 index 0000000..aa39d81 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260902-dashboard-mobile-share-4e91c7.md @@ -0,0 +1,60 @@ +# Task: Adapt leadership dashboard for mobile sharing + +## Identity + +- Task ID: 20260902-dashboard-mobile-share-4e91c7 +- Mode: Feature +- Branch: codex/20260902-dashboard-mobile-share-4e91c7-dashboard-mobile-share-4e91c7 +- Worktree: /Users/inmanx/Documents/lwltAPI-dashboard-mobile-share-4e91c7 +- Base commit: 63b387f67998410d02c2c3493c3e1b2ee8f2f108 +- Owner: codex +- Status: Ready for integration + +## Scope + +- Carry the accepted dashboard card correction into this isolated task: unify the first operation-count card with the remaining metrics, keep the selected state clear, and fold the former follow-up state into in-progress. +- Adapt `/operations-dashboard` for phone and portrait-tablet use without changing its leadership-only authorization boundary. +- Make the route visually self-contained for direct sharing with leaders by removing unrelated operator navigation and technical status controls from the dashboard shell. +- Replace the desktop-only task table interaction with a responsive mobile card presentation while preserving the same data, filters, pagination, and task-detail drill-through. +- Add focused regression coverage and verify the real layout at phone, tablet, and desktop viewports. + +## Intent And Constraints + +- “Standalone sharing” means an authenticated, focused dashboard URL for administrators and team leads; it does not authorize an anonymous or public share link. +- Preserve the existing dashboard API, business-facing terminology, ranking semantics, filter behavior, and dynamic nice-scale bars. +- Keep every mobile control touch-friendly, avoid page-level horizontal overflow, respect device safe areas, and prevent the background dashboard from scrolling while the task-detail overlay is open. +- Preserve all task, account, ERP, deployment, and runtime-data boundaries. Do not access ERP, restart services, mutate runtime data, or deploy this feature. +- Do not alter business adaptation routes, schemas, mappings, extension sources, release artifacts, or canonical project memory from this Feature task. + +## Outcome + +- Brought the prior dashboard card correction into the feature: all five metrics now share one treatment, the active filter uses a light-green selected state with `aria-pressed`, and no visible follow-up card or filter remains. Waiting-for-input work is counted and queried under in-progress. +- Added a dedicated leadership header and return link, enabled `viewport-fit=cover`, and hid the main operator navigation, automation control, AI status, plugin status, and their popover on the dashboard route. Password and logout controls remain available. +- Added a responsive layout through 920 px: a horizontally swipeable metric strip, one-column fixed-height rankings with larger touch targets, two-column filters, and compact safe-area-aware chrome that fits down to a 320 px viewport. +- Converted task rows into labeled mobile cards without duplicating data or changing the desktop table. Each cell receives a semantic `data-label`; input and output remain visually distinct, and status stays visible at the card edge. +- Made task details a safe-area-aware near-full-screen mobile dialog, froze background scrolling while it is open, and restored scrolling on close. +- Preserved the desktop two-ranking layout, dynamic count scales, filters, pagination, detail drill-through, and leadership authorization. Updated static cache keys and their regression expectation. + +## Verification + +- `node --check LianSyn-platform/app.js`: passed. +- Focused dashboard regression `LianSyn-platform/app-operations-dashboard.test.mjs`: 4/4 passed. +- `node --test tools/repository-hygiene.test.mjs`: 10/10 passed. +- TypeScript no-emit check: passed. +- Control-plane suite: 153/153 passed. +- Legacy and platform suite: 264/264 passed. +- TypeScript build: passed. +- `git diff --check`: passed. +- Isolated non-persistent browser preview at `http://127.0.0.1:8893/operations-dashboard?mobile-preview=1` used the actual feature HTML, CSS, and JavaScript with business-safe mock data. +- Visual checks passed at 320×740, 390×844, 768×1024, and 1440×900. Document, body, shell, dashboard page, and filter widths matched the viewport at 320, 390, and 768 px; only the intended metric strip had horizontal overflow. +- Browser interaction verified metric selection rendering, filter layout, mobile task cards, task-detail open/close, background scroll locking, and desktop two-column rankings. Browser console error count was zero. +- `check_project_docs.py`: passed. +- `check_doc_drift.py --task-id 20260902-dashboard-mobile-share-4e91c7`: passed with only the owned task record under `.project-docs`. + +## Follow-ups + +- Integrate this isolated feature commit before validating the normal authenticated `/operations-dashboard` route against live control-plane data. No deployment or public-link behavior is included in this task. + +## Promotion Candidates + +- None. This is a bounded responsive and presentation change within the accepted leadership-dashboard behavior. diff --git a/LianSyn-platform/app-operations-dashboard.test.mjs b/LianSyn-platform/app-operations-dashboard.test.mjs new file mode 100644 index 0000000..3896a23 --- /dev/null +++ b/LianSyn-platform/app-operations-dashboard.test.mjs @@ -0,0 +1,61 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; + +const [app, index, styles, taskService] = await Promise.all([ + readFile(new URL('./app.js', import.meta.url), 'utf8'), + readFile(new URL('./index.html', import.meta.url), 'utf8'), + readFile(new URL('./styles.css', import.meta.url), 'utf8'), + readFile(new URL('../control-plane/src/task-service.ts', import.meta.url), 'utf8') +]); + +test('dashboard summary cards share one visual treatment and expose an accessible selected state', () => { + assert.match(app, /label: '操作次数'[^\n]+tone: 'total'/); + assert.doesNotMatch(app, /tone: 'primary'/); + assert.match(app, /card\.setAttribute\('aria-pressed', String\(isSelected\)\)/); + assert.match(styles, /\.operations-dashboard-summary\s*{[\s\S]*?grid-template-columns: repeat\(5, minmax\(0, 1fr\)\)/); + assert.match(styles, /\.operations-dashboard-stat\.is-active\s*{[\s\S]*?background: var\(--dashboard-accent-soft\);[\s\S]*?transform: none;/); + assert.doesNotMatch(styles, /operations-dashboard-stat-primary/); +}); + +test('waiting-for-input work is folded into in-progress instead of exposing a follow-up state', () => { + assert.doesNotMatch(index, /