feat: Pruned dead UI, renamed view toggle to Oldest/Newest first, added…
- "frontend/src/pages/AdminPipeline.tsx" - "frontend/src/App.css" GSD-Task: S04/T02
This commit is contained in:
parent
44c0df6e08
commit
9dfa568bb3
5 changed files with 148 additions and 22 deletions
|
|
@ -15,7 +15,7 @@ Steps:
|
||||||
- Estimate: 45m
|
- Estimate: 45m
|
||||||
- Files: frontend/src/api/public-client.ts, frontend/src/pages/AdminPipeline.tsx, frontend/src/App.css
|
- Files: frontend/src/api/public-client.ts, frontend/src/pages/AdminPipeline.tsx, frontend/src/App.css
|
||||||
- Verify: ssh ub01 "cd /vmPool/r/repos/xpltdco/chrysopedia && docker compose build chrysopedia-web" succeeds with exit 0
|
- Verify: ssh ub01 "cd /vmPool/r/repos/xpltdco/chrysopedia && docker compose build chrysopedia-web" succeeds with exit 0
|
||||||
- [ ] **T02: Prune dead UI, rename view toggle, add debug indicator on trigger, add review queue cross-link** — Clean up the pipeline page: remove low-value UI elements, rename confusing labels, add debug mode context to trigger button, and add cross-navigation to review queue.
|
- [x] **T02: Pruned dead UI, renamed view toggle to Oldest/Newest first, added debug indicator on trigger button, added review queue cross-link on video cards** — Clean up the pipeline page: remove low-value UI elements, rename confusing labels, add debug mode context to trigger button, and add cross-navigation to review queue.
|
||||||
|
|
||||||
Steps:
|
Steps:
|
||||||
1. In EventLog component, rename 'Head'/'Tail' buttons to 'Oldest first'/'Newest first'. The buttons are in the `.pipeline-events__view-toggle` div. Just change the button text.
|
1. In EventLog component, rename 'Head'/'Tail' buttons to 'Oldest first'/'Newest first'. The buttons are in the `.pipeline-events__view-toggle` div. Just change the button text.
|
||||||
|
|
|
||||||
24
.gsd/milestones/M007/slices/S04/tasks/T01-VERIFY.json
Normal file
24
.gsd/milestones/M007/slices/S04/tasks/T01-VERIFY.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"taskId": "T01",
|
||||||
|
"unitId": "M007/S04/T01",
|
||||||
|
"timestamp": 1774899251289,
|
||||||
|
"passed": false,
|
||||||
|
"discoverySource": "task-plan",
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"command": "ssh ub01 \"cd /vmPool/r/repos/xpltdco/chrysopedia",
|
||||||
|
"exitCode": 2,
|
||||||
|
"durationMs": 5,
|
||||||
|
"verdict": "fail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "docker compose build chrysopedia-web\" succeeds with exit 0",
|
||||||
|
"exitCode": 2,
|
||||||
|
"durationMs": 5,
|
||||||
|
"verdict": "fail"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"retryAttempt": 1,
|
||||||
|
"maxRetries": 2
|
||||||
|
}
|
||||||
76
.gsd/milestones/M007/slices/S04/tasks/T02-SUMMARY.md
Normal file
76
.gsd/milestones/M007/slices/S04/tasks/T02-SUMMARY.md
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
---
|
||||||
|
id: T02
|
||||||
|
parent: S04
|
||||||
|
milestone: M007
|
||||||
|
provides: []
|
||||||
|
requires: []
|
||||||
|
affects: []
|
||||||
|
key_files: ["frontend/src/pages/AdminPipeline.tsx", "frontend/src/App.css"]
|
||||||
|
key_decisions: ["Lifted debug mode state from DebugModeToggle to AdminPipeline parent so trigger button can show debug indicator"]
|
||||||
|
patterns_established: []
|
||||||
|
drill_down_paths: []
|
||||||
|
observability_surfaces: []
|
||||||
|
duration: ""
|
||||||
|
verification_result: "Docker build of chrysopedia-web succeeded with exit 0. TypeScript compiled cleanly, Vite bundled 48 modules, nginx image produced."
|
||||||
|
completed_at: 2026-03-30T19:36:31.974Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# T02: Pruned dead UI, renamed view toggle to Oldest/Newest first, added debug indicator on trigger button, added review queue cross-link on video cards
|
||||||
|
|
||||||
|
> Pruned dead UI, renamed view toggle to Oldest/Newest first, added debug indicator on trigger button, added review queue cross-link on video cards
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
---
|
||||||
|
id: T02
|
||||||
|
parent: S04
|
||||||
|
milestone: M007
|
||||||
|
key_files:
|
||||||
|
- frontend/src/pages/AdminPipeline.tsx
|
||||||
|
- frontend/src/App.css
|
||||||
|
key_decisions:
|
||||||
|
- Lifted debug mode state from DebugModeToggle to AdminPipeline parent so trigger button can show debug indicator
|
||||||
|
duration: ""
|
||||||
|
verification_result: passed
|
||||||
|
completed_at: 2026-03-30T19:36:31.974Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# T02: Pruned dead UI, renamed view toggle to Oldest/Newest first, added debug indicator on trigger button, added review queue cross-link on video cards
|
||||||
|
|
||||||
|
**Pruned dead UI, renamed view toggle to Oldest/Newest first, added debug indicator on trigger button, added review queue cross-link on video cards**
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
|
||||||
|
Six changes to AdminPipeline.tsx: renamed Head/Tail view toggle to Oldest first/Newest first; removed duplicate event-level refresh button; removed truncated ID from expanded detail; lifted debugMode state to parent component so trigger button shows '▶ Trigger (debug)' when active; added '→ Moments' cross-link on each video card linking to /admin/review with accent-colored subtle styling.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Docker build of chrysopedia-web succeeded with exit 0. TypeScript compiled cleanly, Vite bundled 48 modules, nginx image produced.
|
||||||
|
|
||||||
|
## Verification Evidence
|
||||||
|
|
||||||
|
| # | Command | Exit Code | Verdict | Duration |
|
||||||
|
|---|---------|-----------|---------|----------|
|
||||||
|
| 1 | `ssh ub01 "cd /vmPool/r/repos/xpltdco/chrysopedia && docker compose build chrysopedia-web"` | 0 | ✅ pass | 5500ms |
|
||||||
|
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
Review queue cross-link goes to /admin/review without video-specific filtering since the review queue doesn't support URL-based video filtering yet.
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `frontend/src/pages/AdminPipeline.tsx`
|
||||||
|
- `frontend/src/App.css`
|
||||||
|
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
Review queue cross-link goes to /admin/review without video-specific filtering since the review queue doesn't support URL-based video filtering yet.
|
||||||
|
|
@ -2776,6 +2776,20 @@ a.app-footer__repo:hover {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pipeline-video__review-link {
|
||||||
|
color: var(--color-accent);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-video__review-link:hover {
|
||||||
|
opacity: 1;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.pipeline-video__actions {
|
.pipeline-video__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.375rem;
|
gap: 0.375rem;
|
||||||
|
|
|
||||||
|
|
@ -245,16 +245,15 @@ function EventLog({ videoId }: { videoId: string }) {
|
||||||
className={`pipeline-events__view-btn${viewMode === "head" ? " pipeline-events__view-btn--active" : ""}`}
|
className={`pipeline-events__view-btn${viewMode === "head" ? " pipeline-events__view-btn--active" : ""}`}
|
||||||
onClick={() => { setViewMode("head"); setOffset(0); }}
|
onClick={() => { setViewMode("head"); setOffset(0); }}
|
||||||
>
|
>
|
||||||
Head
|
Oldest first
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={`pipeline-events__view-btn${viewMode === "tail" ? " pipeline-events__view-btn--active" : ""}`}
|
className={`pipeline-events__view-btn${viewMode === "tail" ? " pipeline-events__view-btn--active" : ""}`}
|
||||||
onClick={() => { setViewMode("tail"); setOffset(0); }}
|
onClick={() => { setViewMode("tail"); setOffset(0); }}
|
||||||
>
|
>
|
||||||
Tail
|
Newest first
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn--small btn--secondary" onClick={() => void load()}>↻ Refresh</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pipeline-events__list">
|
<div className="pipeline-events__list">
|
||||||
|
|
@ -368,28 +367,21 @@ function WorkerStatus() {
|
||||||
|
|
||||||
// ── Debug Mode Toggle ────────────────────────────────────────────────────────
|
// ── Debug Mode Toggle ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function DebugModeToggle() {
|
function DebugModeToggle({
|
||||||
const [debugMode, setDebugModeState] = useState<boolean | null>(null);
|
debugMode,
|
||||||
|
onDebugModeChange,
|
||||||
|
}: {
|
||||||
|
debugMode: boolean | null;
|
||||||
|
onDebugModeChange: (mode: boolean) => void;
|
||||||
|
}) {
|
||||||
const [debugLoading, setDebugLoading] = useState(false);
|
const [debugLoading, setDebugLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let cancelled = false;
|
|
||||||
fetchDebugMode()
|
|
||||||
.then((res) => {
|
|
||||||
if (!cancelled) setDebugModeState(res.debug_mode);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// silently fail — toggle stays hidden
|
|
||||||
});
|
|
||||||
return () => { cancelled = true; };
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
async function handleToggle() {
|
async function handleToggle() {
|
||||||
if (debugMode === null || debugLoading) return;
|
if (debugMode === null || debugLoading) return;
|
||||||
setDebugLoading(true);
|
setDebugLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await setDebugMode(!debugMode);
|
const res = await setDebugMode(!debugMode);
|
||||||
setDebugModeState(res.debug_mode);
|
onDebugModeChange(res.debug_mode);
|
||||||
} catch {
|
} catch {
|
||||||
// swallow — leave previous state
|
// swallow — leave previous state
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -463,6 +455,7 @@ export default function AdminPipeline() {
|
||||||
const [actionLoading, setActionLoading] = useState<string | null>(null);
|
const [actionLoading, setActionLoading] = useState<string | null>(null);
|
||||||
const [actionMessage, setActionMessage] = useState<{ id: string; text: string; ok: boolean } | null>(null);
|
const [actionMessage, setActionMessage] = useState<{ id: string; text: string; ok: boolean } | null>(null);
|
||||||
const [activeFilter, setActiveFilter] = useState<string | null>(null);
|
const [activeFilter, setActiveFilter] = useState<string | null>(null);
|
||||||
|
const [debugMode, setDebugModeState] = useState<boolean | null>(null);
|
||||||
|
|
||||||
const load = useCallback(async () => {
|
const load = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -481,6 +474,18 @@ export default function AdminPipeline() {
|
||||||
void load();
|
void load();
|
||||||
}, [load]);
|
}, [load]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
fetchDebugMode()
|
||||||
|
.then((res) => {
|
||||||
|
if (!cancelled) setDebugModeState(res.debug_mode);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// silently fail — toggle stays hidden
|
||||||
|
});
|
||||||
|
return () => { cancelled = true; };
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleTrigger = async (videoId: string) => {
|
const handleTrigger = async (videoId: string) => {
|
||||||
setActionLoading(videoId);
|
setActionLoading(videoId);
|
||||||
setActionMessage(null);
|
setActionMessage(null);
|
||||||
|
|
@ -538,7 +543,7 @@ export default function AdminPipeline() {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="admin-pipeline__header-right">
|
<div className="admin-pipeline__header-right">
|
||||||
<DebugModeToggle />
|
<DebugModeToggle debugMode={debugMode} onDebugModeChange={setDebugModeState} />
|
||||||
<WorkerStatus />
|
<WorkerStatus />
|
||||||
<button className="btn btn--secondary" onClick={() => void load()} disabled={loading}>
|
<button className="btn btn--secondary" onClick={() => void load()} disabled={loading}>
|
||||||
↻ Refresh
|
↻ Refresh
|
||||||
|
|
@ -588,6 +593,14 @@ export default function AdminPipeline() {
|
||||||
<span className="pipeline-video__time">
|
<span className="pipeline-video__time">
|
||||||
{formatDate(video.last_event_at)}
|
{formatDate(video.last_event_at)}
|
||||||
</span>
|
</span>
|
||||||
|
<a
|
||||||
|
className="pipeline-video__review-link"
|
||||||
|
href="/admin/review"
|
||||||
|
title={`Review moments from ${video.creator_name}`}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
→ Moments
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pipeline-video__actions" onClick={(e) => e.stopPropagation()}>
|
<div className="pipeline-video__actions" onClick={(e) => e.stopPropagation()}>
|
||||||
|
|
@ -597,7 +610,7 @@ export default function AdminPipeline() {
|
||||||
disabled={actionLoading === video.id}
|
disabled={actionLoading === video.id}
|
||||||
title="Retrigger pipeline"
|
title="Retrigger pipeline"
|
||||||
>
|
>
|
||||||
{actionLoading === video.id ? "…" : "▶ Trigger"}
|
{actionLoading === video.id ? "…" : debugMode ? "▶ Trigger (debug)" : "▶ Trigger"}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn btn--small btn--danger"
|
className="btn btn--small btn--danger"
|
||||||
|
|
@ -619,7 +632,6 @@ export default function AdminPipeline() {
|
||||||
{expandedId === video.id && (
|
{expandedId === video.id && (
|
||||||
<div className="pipeline-video__detail">
|
<div className="pipeline-video__detail">
|
||||||
<div className="pipeline-video__detail-meta">
|
<div className="pipeline-video__detail-meta">
|
||||||
<span>ID: {video.id.slice(0, 8)}…</span>
|
|
||||||
<span>Created: {formatDate(video.created_at)}</span>
|
<span>Created: {formatDate(video.created_at)}</span>
|
||||||
<span>Updated: {formatDate(video.updated_at)}</span>
|
<span>Updated: {formatDate(video.updated_at)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue