Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .loopdeck/decisions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,13 @@

_Older decisions archived to [decisions-archive.md](./decisions-archive.md)._

## 2026-08-12 — Rail pin state is a plain per-machine bool; night-run badge is a run-plan-derived placeholder

- **Status**: accepted
- **Context**: `prd-rail-corridor-shell` Phase 1's two open questions (pin trigger UI, pin storage) and its dependency on `prd-detail-drawer`'s not-yet-run night-run spike were pre-answered at run queue time, not left for this loop to guess.
- **Consequences**: Pin/unpin lives on the door itself as a right-click context menu (no room card exists until Phase 2) — a plain `pinned: bool` on `ProjectEntry` (`config.rs`), mirroring the existing `autonomous` flag exactly, no new storage design. The night-run badge derives from whether the project has an active/queued `.loopdeck/run-plan.yaml` (`hasActiveOrQueuedRun` in `src/lib/rail.ts`) — an explicit placeholder, not the real `RunState` variant `prd-night-run-surfaces` may eventually add once its drawer spike lands.
- **Detail**: `.loopdeck/loops.md` `## Current` (2026-08-12 entry) has the full file/symbol breakdown.

## 2026-08-04 — Run-queue reliability: verdict marker must survive to the final message; branch names are now descriptive

- **Status**: accepted
Expand Down
7 changes: 6 additions & 1 deletion .loopdeck/loops.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,11 @@

## Current

- **Started**: 2026-08-05
- **Started**: 2026-08-12
- **Goal**: `selasar-revamp` / `prd-rail-corridor-shell` Phase 1 (all 4 loops, overnight run) — complete.
- **Status**: completed
- ✅ **`prd-rail-corridor-shell` Phase 1 — Project rail, all 4 loops** (2026-08-12) — New 72px `Rail.tsx` (`src/components/layout/`) replacing `AppShell.tsx`'s feature-first sidebar: one door per registered project (2-letter initials, `src/lib/rail.ts::doorInitials`), sorted last-active-first (`sortByLastActive`). Door glow ties to `RunState` (idle/working/waiting/done) via a tone→CSS-var map + `color-mix` box-shadow. Night-run badge is a placeholder per the run's own pre-answered clarification — derived from an active/queued `.loopdeck/run-plan.yaml` (`hasActiveOrQueuedRun`, polled every 5s per visible door via new `useNightRunStatuses` hook + existing `getRunStatus`), pending `prd-detail-drawer`'s spike for the real representation. Pin/unpin is a right-click context menu on the door (Radix `DropdownMenu` driven by state, not its default click-trigger, so left-click still navigates); backed by a new `pinned: bool` on `ProjectEntry` (`config.rs`) + `set_project_pinned` command, mirroring the existing `autonomous` flag exactly. Rail shows all doors at ≤5 projects; past 5, pinned-only + one fixed overflow door back to the corridor — falling back to the 5 most recently active when nobody has pinned yet (`selectRailDoors`). Settings moved to a gear icon at the rail's foot (`/settings` route unchanged); the old sidebar's brand mark and "Local only v0.2.0" footer were dropped per the run's answers, with the version badge relocated into the shared header instead (visible on every page, not just the rail). `onNavClick` prop plumbing (AppShell/router.tsx) deleted as dead code — its only branch (`/import`) had no live caller since the Dashboard redesign replaced that nav link with a dropdown. 8 new frontend tests (`tests/frontend/rail.test.mjs`, pure logic — `doorInitials`/`sortByLastActive`/`selectRailDoors`/`hasActiveOrQueuedRun`) since this repo's `node:test` runner has no DOM. Gates green: fmt/clippy(lib, `-D warnings`)/test(596 passed, unchanged — no new Rust tests, matching the untested `set_project_autonomous` precedent)/`tsc --noEmit`/`npm run build`. **Not verified live**: this is a Tauri desktop app with no `_electron`/Playwright driver in this repo and no browser-mode IPC mock, so a real click-through (door glow colors, right-click pin menu, overflow threshold) needs a human with `npm run tauri dev` — same documented gap as every prior UI-only loop in this file.
- **Previous** (2026-08-05):
- **Goal**: `selasar-revamp` / `prd-rebrand-tokens` — complete (all 3 phases). Phase 2 rebrand: full-repo audit + rename of window title, wordmark, and in-app copy to "Selasar" (`.loopdeck/` + `project.yaml` untouched). Phase 3 verification: visual pass in light/dark/system-auto over every existing screen found and fixed 6 sub-4.5:1 WCAG contrast defects in `src/styles.css` (light warning/success/destructive darkened, dark muted-foreground + primary lightened, dark destructive-foreground → ink), and `npx tsc --noEmit` clean with "Selasar" statically confirmed in the window title bar.
- **Status**: completed
- ✅ **PRD delivery order — `order:` frontmatter, migrate/reorder commands, and two epic-sort bug fixes** (2026-08-06, ad-hoc user request, not tied to a docs/epics PRD, branch `claude/debug-vite-components-a36688`) — Added optional `order: u32` PRD frontmatter (`epic.rs`); `read_prds` sort key is `order` → README `## PRD Index` table position → filename, read-only (a first version auto-backfilled on read and corrupted this repo's own `docs/epics/**/*.md` via `cargo test`'s dogfood test — reverted). Two new explicit commands: `migrate_prd_order` (backfill missing values only, idempotent) and `set_prd_order` (full drag-to-reorder rewrite, permutation-validated). New `PrdReorderDialog.tsx` wired into `EpicsPanel.tsx` (reorder button before the edit pencil) — built on plain pointer events, not HTML5 `draggable`, after discovering Tauri's window-level drag-drop (used by `Chat.tsx`/`ImportFlow.tsx`) silently swallows `dragover`/`drop`. Also fixed two real epic-ordering bugs surfaced along the way: `EpicsView.tsx`'s cross-project milestone merge wasn't actually sorted (stale comment claimed it was), and `EpicsPanel.tsx`'s per-project epic list had no sort at all. Both now use a shared `sortEpics` helper (`lib/utils.ts`) — status-primary (`in_progress` → `proposed` → `abandoned` → `completed`), milestone ascending (numeric) secondary. Gates green: `cargo test` 596 passed (61 in `epic.rs`), `tsc --noEmit` clean. See decision of same date. Not verified live in the running app — Tauri IPC needs the real webview, and the dev-server port was held by another session throughout.
Expand DownExpand Up@@ -64,6 +68,7 @@
- **Independent delivery (2026-07-27, worktree `loopdeck-dashboard-redesign-effort-69cb9b`)**: Redesigned the Dashboard to match a reference mockup (`LoopDeck 0.3.1 — Workspace Dashboard Mockup.html`, user-supplied). New top bar with a real ⌘K command palette (`CommandPalette.tsx`, built on the previously-installed-but-unwired `ui/command.tsx`/`cmdk` — closes the P6 backlog item that flagged the old decorative ⌘K hint as misleading); sidebar nav regrouped into Work/Memory sections with a "Local only · v0.2.0" status footer (`AppShell.tsx`). New "Needs attention" panel (`AttentionPanel.tsx`) sourced from live state only — pending permission/question/plan prompts (`usePendingInteractions`) and `run_state === "working"` projects — renders nothing when empty, no filler card. Project grid replaced with a project list (`ProjectList.tsx`, `ProjectCard.tsx` deleted — its only consumer was `Dashboard.tsx`) showing a derived state pill (Waiting/Working/Ready/Idle) and a state-dependent focus line. New "Today" timeline (`TodayPanel.tsx`) shares a newly-extracted `useActivityEvents` hook (`src/hooks/useActivityEvents.tsx`) with `ActivityFeed.tsx`, deduping ~150 lines of turn/decision/loop collector logic that was previously inline only in the Activity page. Backend: `ProjectEntry` gained ephemeral `next_steps_total`/`next_steps_done` fields (`config.rs`), parsed from `.loopdeck/loops.md`'s Next Steps checklist via the existing `memory::parse_loops` and wired into `import_project`/`list_projects`'s existing blocking closures alongside the `current_loop` read — same consistency level as that field; `rescan_project`/`get_project` intentionally left untouched (they don't refresh `current_loop` live either). No re-theme was needed — the app's existing violet/oklch design tokens already matched the mockup's palette intent. Scoped out per the plan: the mockup's mobile bottom-nav bar (desktop Tauri app), its non-functional "Filter ⌄" button, and its fabricated "N agents available"/per-card elapsed-time stats (replaced with real derived counts, or dropped where no real data exists). **Verified:** `cargo fmt --check` / `cargo clippy --lib -- -D warnings` / `cargo test --lib` (436 passed) all green; `tsc --noEmit` and `npm run build` clean; visually smoke-tested via the Vite dev server (shell layout, sidebar grouping, command palette open + navigate, light/dark theme toggle all confirmed working). **Not verified:** real project-data rendering end-to-end (`invoke()` rejects outside the native Tauri shell, so `list_projects` etc. can't be exercised from a plain browser) — needs a manual `npm run tauri dev` pass.

## Next Steps
- [ ] Review & merge: https://github.com/suprie/loopdeck/pull/84 (Project Rail — prd-rail-corridor-shell Phase 1)
- [ ] Review & merge: https://github.com/suprie/loopdeck/pull/71
- [ ] Review & merge: https://github.com/suprie/loopdeck/pull/66 (docs-accuracy: audit + rewrite PRD.md/CLAUDE.md)
- [ ] Review & merge: https://github.com/suprie/loopdeck/pull/new/feat/prd-wake-up
Expand Down
1 change: 1 addition & 0 deletions docs/epics/optimization/prd-docs-accuracy.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@ description: >
Audit docs/PRD.md and CLAUDE.md against the code that actually ships, then
rewrite both so neither states a non-goal the code violates or a file
structure that no longer exists. No code changes — documentation only.
order: 10
---

# PRD — Docs Accuracy
Expand Down
1 change: 1 addition & 0 deletions docs/epics/optimization/prd-frontend-test-coverage.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ description: >
Vite + React 19 stack) and close the three coverage gaps a review flagged
as highest-risk: streaming channels, migration cards, and permission-
approval flows. Not a general coverage mandate — closes named gaps only.
order: 30
---

# PRD — Frontend Test Coverage
Expand Down
1 change: 1 addition & 0 deletions docs/epics/optimization/prd-memory-hygiene.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ description: >
and decisions.md, lower the archive trigger below the 90KB threshold that
already proved insufficient once, and compact current content to fit —
without deleting anything, only archiving it.
order: 40
---

# PRD — Memory Hygiene
Expand Down
1 change: 1 addition & 0 deletions docs/epics/optimization/prd-process-discipline.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ description: >
amendments about amendments — after a review found this pattern in recent
loop history plus a near-miss where an agent almost deleted the
orchestrator by misreading its own memory.
order: 50
---

# PRD — Process Discipline
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ description: >
commands/agent.rs (1731 lines) if it's still oversized once the maps are
unified. The most concurrency-sensitive surface in the app, so this opens
with a spike phase before any code moves.
order: 20
---

# PRD — Session State Consolidation
Expand Down
2 changes: 1 addition & 1 deletion docs/epics/overnight-orchestration/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@
title: Overnight Orchestration
slug: overnight-orchestration
milestone: "0.4.0"
status: in_progress
status: completed
started: 2026-07-27
owner: Suprie
description: >
Expand Down
19 changes: 12 additions & 7 deletions docs/epics/selasar-revamp/prd-rail-corridor-shell.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,24 +62,29 @@ on the existing routed `ProjectDetail` page as an interim target.

## Design

_Stub — pin/unpin trigger placement and the default rail state for an
existing install crossing 5 projects with zero pins are Phase 1 decisions,
not made yet._
Phase 1 decisions resolved during implementation (2026-08-12): pin/unpin is a
right-click context menu on the door itself (no room card exists yet this
phase); an existing install crossing 5 projects with zero pins shows the 5
most recently active as a fallback rather than an empty rail; pin state is a
plain `bool` on the global registry's `ProjectEntry` (per-machine, not
synced). The night-run indicator is a placeholder — derived from whether the
project has an active/queued `RunPlan` (existing run-queue data), pending
`prd-detail-drawer`'s spike settling the real `RunState` representation.

## Phases

### Phase 1 — Project rail

- [] Build a `Rail` component (72px icon strip, one door per project, 2-letter
- [x] Build a `Rail` component (72px icon strip, one door per project, 2-letter
initials) replacing `AppShell.tsx`'s current sidebar markup.
- [] Wire each door's glow color to that project's `RunState`
- [x] Wire each door's glow color to that project's `RunState`
(working/waiting/done/idle) plus a distinct night-run indicator,
reusing the state derivation already in `AttentionPanel.tsx` /
`useAttentionItems`.
- [] Add a pin/unpin affordance per project, and rail logic that shows all
- [x] Add a pin/unpin affordance per project, and rail logic that shows all
projects at 5 or fewer, or pinned-only plus one overflow door (back to
the corridor) past 5.
- [] Move the settings entry point to a gear icon at the rail's foot,
- [x] Move the settings entry point to a gear icon at the rail's foot,
preserving the existing `/settings` route.

### Phase 2 — Corridor room-card list
Expand Down
4 changes: 2 additions & 2 deletions docs/epics/selasar-revamp/prd-rebrand-tokens.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@
prd: prd-rebrand-tokens
epic: selasar-revamp
milestone: "0.5.0"
status: proposed
status: completed
description: >
Replace the app's Tailwind-v4 design tokens with the mockup's paper/ink/
teak palette and serif display font (light default, full dark pair), and
Expand DownExpand Up@@ -86,7 +86,7 @@ occurrence sites are Phase 1/Phase 2 outputs, not decided yet._

### Phase 3 — Verification

- [x] Visual pass in light, dark, and system-auto against every existing
- [x] `selasar-revamp/visual-pass-in-light-dark-and-system-auto-against-every-existing` Visual pass in light, dark, and system-auto against every existing
screen (Dashboard, ProjectDetail tabs, Settings, Activity, Loops,
Decisions, Epics), confirming no contrast regression on destructive,
warning, or focus states.
Expand Down
1 change: 1 addition & 0 deletions index.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Selasar</title>
<script src="http://localhost:8097"></script>
</head>
<body>
<div id="root"></div>
Expand Down
28 changes: 28 additions & 0 deletions src-tauri/src/commands/project.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,6 +97,7 @@ async fn register_directory(
uncommitted: git_info.uncommitted.into(),
run_state: RunState::Idle,
autonomous: false,
pinned: false,
next_steps_total,
next_steps_done,
};
Expand DownExpand Up@@ -385,6 +386,33 @@ pub async fn set_project_autonomous(
Ok(())
}

/// Set the per-project rail-pin flag in the config registry.
///
/// Past 5 registered projects, the 72px rail shows pinned projects only (plus
/// a fixed overflow door back to the corridor) — see `prd-rail-corridor-shell`
/// Phase 1.
#[tauri::command]
pub async fn set_project_pinned(
path: String,
pinned: bool,
state: State<'_, AppState>,
) -> Result<(), AppError> {
debug!("set_project_pinned called for path: {path}, pinned: {pinned}");
let root = {
let config = state.config.lock().map_err(|_| AppError::LockError)?;
paths::resolve_registered_root(&config, &path)?
};
{
let mut config = state.config.lock().map_err(|_| AppError::LockError)?;
if let Some(entry) = config.find_by_path_mut(&root) {
entry.pinned = pinned;
config.save()?;
}
}
info!("set_project_pinned complete for: {path} → {pinned}");
Ok(())
}

/// Remove a project from the registry.
/// Does NOT delete the `.loopdeck/` directory or any project files.
#[tauri::command]
Expand Down
7 changes: 7 additions & 0 deletions src-tauri/src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -228,6 +228,12 @@ pub struct ProjectEntry {
/// `skip_serializing_if` keeps the registry tidy for the common case.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub autonomous: bool,
/// Pinned to the rail (`prd-rail-corridor-shell` Phase 1): past 5
/// registered projects, only pinned projects (plus a fixed overflow door)
/// show in the 72px rail. Per-machine, stored in the global registry —
/// not synced. Older configs without this field deserialize to `false`.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub pinned: bool,
/// Total `## Next Steps` checklist items parsed from `.loopdeck/loops.md`.
/// Re-read at every `list_projects`/`import_project` call (same treatment
/// as `current_loop`: doesn't gate whether a save happens, but is always
Expand DownExpand Up@@ -272,6 +278,7 @@ impl Default for ProjectEntry {
uncommitted: UncommittedStats::default(),
run_state: RunState::Idle,
autonomous: false,
pinned: false,
next_steps_total: 0,
next_steps_done: 0,
}
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -167,6 +167,7 @@ pub fn run() {
commands::project::get_project,
commands::project::update_description,
commands::project::set_project_autonomous,
commands::project::set_project_pinned,
commands::project::remove_project,
commands::project::open_in_finder,
commands::project::open_in_terminal,
Expand Down
Loading