Board is now a clean master-task view:
- GetBoardAsync returns only top-level (master) tasks; child-tasks render
nested inside their parent card instead of as separate column cards, so a
big task split into many sub-tasks stays one card (orphans treated as master)
- New DoneChildTaskCount on the DTO for real progress bars
- Child/detail consumers (GetChildren endpoint, TaskBridgeService) query
children directly instead of scraping the flat board
Stall watchdog (replaces destructive auto-reset):
- StaleTaskRecoveryService.FlagStalledInProgressTasksAsync marks In-progress
tasks with no activity past the threshold as stalled (activity event +
Iris notification) WITHOUT resetting the column — no work is discarded.
Idempotent: a task is not re-flagged until real progress happens
- BackgroundService now runs this watchdog (TaskRecovery:StalledMinutes=40,
interval 10m); hard reset kept only on the explicit manual endpoint
Review flow (Bao/Iris only):
- POST tasks/{id}/approve (Review -> Done)
- POST tasks/{id}/request-changes (Review -> target, mandatory comment,
ExpectedFrom=iris, notifies Iris)
Frontend:
- BoardCard component: master card with ball chip (who has it), progress from
children, expand to show children grouped by agent with per-child state +
stalled marker, stalled chip on the master, review action buttons
- Request-changes modal; tasks store approveReview/requestChanges actions
Tests: watchdog flag/idempotency + review threshold; 135 backend tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Shell:
- One shared NexusLayout for ALL routes (dashboard + pages): compact 68px
icon rail with hover-expand overlay, replaces both old sidebars + topbars
- Single flat nav source (railNav) — same menu everywhere incl. Settings
- Removed dead shell components (AppSidebar, AppHeader, Topbar, NavGroup,
NavItem, ModuleView) and dead nav routes; App.vue is now just RouterView
Live updates:
- Fix SSE loop in DashboardController: PeriodicTimer.WaitForNextTickAsync and
ChannelReader.ReadAsync were re-invoked while pending — every published
update threw InvalidOperationException and killed ALL live streams
- liveSync store: treat graceful stream close as disconnect (was stuck
connected=true with polling stopped -> page frozen until manual reload),
fast first retry, heartbeat watchdog (65s), reconnect on online/visibility
- One app-wide SSE connection owned by the layout instead of per-view
connect/disconnect churn; removed duplicate live-sync.ts store
Performance:
- Board endpoint: drop nested childTasks duplication (counts stay) — payload
104KB -> 65KB; SSE snapshots shrink equally
- nginx: gzip for JSON/JS/CSS (board 18KB, bundle 95KB over the wire);
text/event-stream excluded to keep SSE unbuffered
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Map legacy v1 CSS variables (--nx-*, --panel, --text-*, --surface*) to V2 tokens in nexus-tokens.css
- Restyle global shell (main.css), AppSidebar, AppHeader to match V2 Sidebar/Topbar (glass, gradients, Space Grotesk)
- Add GalaxyBackground to the v1 shell in App.vue
- Replace hardcoded v1 hex colors with V2 tokens in all deviating views (Agents, Calendar, Docs, Incidents, Memory, Notifications, ProjectDetail, Security, Team, TaskDetail, Settings)
- Keep JS status colors as hex where alpha suffixes are concatenated (AgentsIndex, Team, Notifications)
- Add Settings nav item (System group) + gear icon to V2 sidebar so it shows on the dashboard
- No component or layout structure changes; LoginView and TaskBoardView untouched
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root cause: /api/bridge/health and /api/dashboard/live returned 404 live because
the source files existed on disk but were never committed to git. The CD pipeline
deploys from committed main, so the deployed containers lacked these endpoints.
Changes:
- Add GatewayBridgeController with /api/bridge/health endpoint
- Add DashboardController Live() SSE endpoint for /api/dashboard/live
- Add LiveUpdateService (in-memory pub/sub for SSE updates)
- Add TaskBridgeService (structured agent-to-backend bridge)
- Add nginx routing blocks for /api/bridge/ and /api/dashboard/live
- Add host-level nginx-nexus.conf blocks for bridge + live pass-through
- Update ServiceCollectionExtensions with DI registrations
- Update Dashboard.cs model with SSE-related DTOs
Verification after deploy:
curl https://nexus.noveria.net/api/bridge/health → 200 JSON
curl https://nexus.noveria.net/api/dashboard/live → 200 SSE stream
Root cause: Dual-source architecture for owner password (Gitea secret
ENV_OWNER_PASSWORD vs host .env OWNER_PASSWORD) caused drift when
the DB was ever re-seeded or the volume recreated.
Changes:
- Add SeedAudit entity + migration to track one-time seed operations
- EnsureDatabaseAsync checks SeedAudit BEFORE seeding — owner is never
re-created even if the Users table is wiped
- Deploy and rollback workflows now read OWNER_PASSWORD from the host's
persistent .env (single source of truth) instead of Gitea secrets
- compose.yaml documented: OWNER_PASSWORD only used during initial seed
- Cleanup: .gitignore extended for core dumps, changelog/deployment.md
updated with 2026-06-20 session notes
After this fix the DB is the single source of truth for the owner
password after initial seed. The host .env is the single reference
for the initial value.