# Workspace dashboard

## overview.md
```yaml
id: workspace-dashboard
title: Workspace dashboard
area: ops
summary: The public site doubles as Kris's private working surface — an attention-router for supervising many agents, not a project-management tool he fills in by hand.
repo: n/a — this workspace (site/, epics/)
depends_on: []
```

## What this is

An extension of the existing public site (`public-site-and-comms`) into a working surface Kris actually uses day to day: he clicks an epic, kicks off a Claude Code session against it via the task runner, and — over time — nearly all epic work is moved forward by agents rather than by Kris typing into files himself. When an agent hits a real question or a blocker, it **surfaces it** instead of guessing or stalling silently. Kris's job becomes: log in, see what's asking for him, answer it, unblock the agent, and spot-check PoC/deliverable outputs to confirm things are tracking well.

**This is an attention-router for a human supervising many agents, not a project-management tool.** The distinction matters for every design choice below: Kris is not the one keeping the board up to date — agents are — and the dashboard's job is to make sure the small number of things that genuinely need him surface immediately, instead of getting lost in a board full of `in-progress` rows he doesn't need to look at.

## Why it exists

Right now, "check on the project" means reading through `epics/*/status.md` by hand, or reading a whole conversation transcript. That doesn't scale past a handful of epics, and it actively works against the goal of letting agents run more of this autonomously — the more epics are agent-driven, the more Kris needs a single place that tells him *specifically* where his attention is needed, not a full status report he has to triage himself every time.

## Design constraints (settled, not open questions)

These aren't preferences — each one heads off a specific failure mode, so they're recorded as constraints an implementation must satisfy, not options to reconsider casually.

### 1. Split read from write

A public, read-only, sanitised view of agent activity is genuinely desirable and safe — "watch a game get built in public by AI agents" is a real asset for the project's public story (see `public-site-and-comms`, `WHY.md`). But the **control surface** — submitting tasks, editing status, triggering agent runs — must be authenticated and entirely separate from that public view. A static site bundle (which is what `site/` builds today) hides nothing: anything shipped to the public build is visible to anyone who looks, so there is no such thing as "client-side-only" gating for the write surface. The write surface needs real server-side auth, which the current static-only site doesn't have and this epic must add deliberately, not bolt on.

### 2. Public input must never reach task execution

Not sandboxed, not even in v1. If an agent with repo write access is consuming open-internet text (a public comment, a public form submission) as part of what feeds into a task prompt, that agent is a prompt-injection target — and the realistic failure mode is a **hostile commit landing in the repo**, not merely wasted compute. So: public suggestions land as **content** — a form that writes a markdown file, or similar (GitHub Discussions and outside PRs stopped being options when the repos went permanently private, decision 2026-08-04 — see `questions/001-repo-visibility.md`) — and **Kris triages them into epics by hand**. No automatic path from "someone typed something on the public site" to "an agent acted on it." This should only be revisited after observing real public traffic and behaviour, not designed around hypothetically.

### 3. Files stay canonical

The dashboard reads and writes the same markdown already in the repo (`epics/*/overview.md`, `status.md`, and whatever new per-epic files this epic adds — see the data model below). It never becomes a second source of truth sitting next to the files. Consequence: **a broken site build must be an inconvenience, not a work stoppage** — agents and Kris can both keep working directly in the files with a plain editor if the dashboard itself is down. This is the same principle `public-site-and-comms` already established for the public content side; this epic extends it to the control side too.

### 4. Publication default is private, opt-in to publish

Task prompts, agent transcripts, and blocker/question text may routinely contain things Kris doesn't want made public — an offhand comment, an unfinished thought, something about family, a business detail. The default for anything new must be **private**, with an explicit action to make a specific thing public. A default of "public unless marked private" would force Kris to self-censor everything he writes to agents, which defeats the point of a low-friction supervision tool.

### 5. Blocker/question surfacing is the core feature — not task submission

It would be easy to build this backwards: a nice task-submission UI first, blockers as an afterthought. The actual primary user need, per the vision above, is **"what needs me, right now?"** — so the data model for that has to be designed first, before anything else:

- Agents write **structured** questions/blockers into the epic folder they're working (format TBD by implementation — likely a `questions/` subfolder or a structured block within `status.md`, needs its own small design pass when this moves to build).
- The dashboard's job is to **aggregate every open question/blocker across every epic into one queue**, sorted by however "needs attention" should be prioritised (age, epic tag, explicit urgency — undecided, flag for build-time design).
- Task submission (kicking off a new agent session against an epic) is real and wanted, but it is not the feature that makes this valuable day to day — the queue is.

## Scope (for when this moves past speccing)

- The blocker/question data model and aggregation queue (constraint 5).
- Authenticated control surface, separate from the public static build (constraint 1).
- A way to kick off a Claude Code / task-runner session against a specific epic from the dashboard.
- Public-content intake (a form-to-markdown-file or similar — not GitHub Discussions/PRs, per the 2026-08-04 private-repos decision) that lands as triage material, never as direct task input (constraint 2).
- Publish/private toggle per item, default private (constraint 4).

## Non-goals

- Not a general project-management tool with sprints, story points, or burndown — it exists to route Kris's attention, nothing more general than that.
- Not building or restructuring anything yet — this epic is spec-only until the blocking audit below is clear and Kris has reviewed the design constraints.
- Not deciding the blocker/question file format yet — noted as a build-time design task, not resolved here.

## FIRST TASK — BLOCKS EVERYTHING ELSE IN THIS EPIC: secrets audit

**No other work in this epic may start until this is done and reviewed by Kris.** The dashboard's entire premise involves more exposure — a public read-only view, an authenticated write surface, potentially opening this repo's visibility — and none of that is safe to build toward until it's confirmed the repo is actually clean.

### What triggered this

An Android keystore password was handled in plain text in past task-runner sessions and past chats. That's the immediate concern, but the audit should not be scoped narrowly to just that one password.

### Scope of the audit

1. Search the **current working tree** of both the `lor-workspace` repo and the `land-of-lor` repo for: any `.keystore`/`.jks` file, any plaintext password/credential string, any API key or token pattern.
2. Search **the full git history** of both repos — not just current `HEAD` — for the same things. **Deleting a file today does not remove it from history**; a password committed once and later removed is still sitting in every commit before the removal, retrievable by anyone with clone access. This means the audit has to walk history (`git log -p`, or a proper secrets-scanning tool run against the full history, not just a working-tree grep) in both repos.
3. For anything found, report **exactly which repo(s) and which commit(s)** it's in — precise enough that rotation and any history-rewrite decision can be made without re-doing the search.

### If anything is found

- **The keystore password (or any other found credential) must be rotated.** A leaked Android signing key is not a routine secret leak: it lets a third party sign malicious packages that users' devices will accept as legitimate updates to an app they already trust and have installed. State this plainly to Kris in the audit's findings, not just as a checkbox — the stakes are the whole reason this blocks the epic.
- **Neither repo may be made public until it is clean.** "Clean" means the secret is gone from history, not just from the current tree — a `git filter-repo`/BFG-style history rewrite (or accepting that the repo simply stays private) may be necessary, and that's a judgment call for Kris once the audit reports exactly what's there.

### The visibility recommendation this audit gated — SUPERSEDED by decision, 2026-08-04

The recommendation that stood here (make `lor-workspace` public, keep `land-of-lor` private) was **decided against by Kris on 2026-08-04**: all lor repos stay **private**, permanently as far as current planning goes — the site's build output is the only public surface. A clean template repo for others to reuse the mechanism may be published someday, but no lor repo will be. Full record: `questions/001-repo-visibility.md`. Consequence for this epic: everything below that leaned on GitHub Discussions, outside PRs, or a browsable public repo (constraint 2's intake examples, task 005's options) now has only the form-to-file style of mechanism available.

## Key open questions

- Exact structured format for agent-written blockers/questions (subfolder vs. a `status.md` block) — a build-time design task, not resolved here.
- Authentication mechanism for the control surface — nothing evaluated yet.
- Prioritisation rule for the attention queue once more than a few blockers exist at once.

## Relevant docs

- `epics/public-site-and-comms/` — the existing public site this epic extends; its "split read from write" groundwork (static public build) is why constraint 1 above is necessary rather than automatic.
- `epics/README.md` — the current filesystem-based mechanism (`overview.md`/`status.md`) this epic's data model builds on rather than replaces.
- `WHY.md` — bears on constraint 4: task prompts touching family/personal material are exactly the kind of content that must default to private.

## status.md
```yaml
updated: 2026-08-12
parked: true
tasks: {"backlog":3,"needs-input":0,"ready":0,"doing":0,"review":0,"done":2}
open_questions: []
tag: parked
next: Parked — the /now queue is good enough for now (Kris, 2026-08-12). Pick one of 003-005 when it stops being enough.
```

## Task summary

2 done · 3 backlog (5 total). Parked. Task 001 (secrets audit) is done — clean findings, keystore rotated to `.secrets/lor2026.keystore`, reviewed 2026-08-04. Task 002 (blocker/question aggregation queue) shipped as `/now`, along with the `needs-review` derived tag, the `review_artifact` task field, gold "needs Kris" chips, epic `next:` lines on the board, and a footer build stamp; Kris reviewed it in use on 2026-08-12 — he cleared the whole queue through it — and signed it off as "good enough for right now."

Parked rather than blocked: tasks 003-005 are real ideas, but nothing is waiting on them and nobody is asking for them yet.

## Open questions

None open. Q001 (repo visibility) answered 2026-08-04: **all lor repos stay private**, the site is the only public surface — see `questions/001-repo-visibility.md`.

## Next

Unpark and pick one of 003 (auth control surface), 004 (depends on 003), or 005 (public intake, form-to-file only) when `/now` stops answering "what needs me, and what's ready for agents?" on its own.

## tasks/ (5)

### workspace-dashboard/001 — Secrets audit — current tree and full git history, both repos
```yaml
id: workspace-dashboard/001
title: Secrets audit — current tree and full git history, both repos
epic: workspace-dashboard
state: done
priority: 1
blocked_by: []
estimate: M
created: 2026-08-05
updated: 2026-08-04
claimed_by: task-runner-session-2026-08-03
claimed_at: 2026-08-03T08:45:55.000Z
delivers: []
review_artifact: null
```

## Findings

See `../001-secrets-audit-findings.md` for the full report. **Result: clean** — no secrets found in either repo's working tree or full git history. One caveat the audit can't resolve: the password that triggered this audit was reportedly typed into a past task-runner session / chat, which lives outside any repo this audit can search — so "clean in git" isn't the same claim as "never exposed."

**Reviewed by Kris 2026-08-04, and the rotation call was made: rotate anyway** (out of caution over the transcript-exposure caveat). New signing key created: `.secrets/lor2026.keystore`, alias `lor`, password in `.secrets/lor2026.keystore.password` — present both locally and in the AI Task Runner container's `.secrets/`. The old `lor.keystore` is superseded (kept only for updating already-installed lor-elementals APKs). Full resolution note at the bottom of the findings file. The visibility question was decided the same day: **all lor repos stay private** — see `../questions/001-repo-visibility.md`.

## What to do

Exactly the scope already specified in `overview.md`'s "FIRST TASK" section — carried across verbatim here, not re-derived:

1. Search the **current working tree** of both `lor-workspace` and `land-of-lor` for: any `.keystore`/`.jks` file, any plaintext password/credential string, any API key or token pattern.
2. Search **the full git history** of both repos (not just `HEAD`) for the same — deleting a file today does not remove it from history. Walk history properly (`git log -p`, or a real secrets-scanning tool against full history), in both repos.
3. Report **exactly which repo(s) and which commit(s)** anything is found in.

## Definition of done

A findings report (clean, or precise: repo + commit + what). If anything is found: state plainly that a leaked Android signing key lets a third party sign malicious packages users' devices will accept as legitimate updates — this is not a routine secret leak. Do not rotate anything or rewrite any git history yourself; that's Kris's call once findings are in. Do not act on the visibility recommendation (see `overview.md`) even if the audit comes back clean — flag it, don't execute it.

## Where the work lands

This workspace repo (the findings report; suggest `epics/workspace-dashboard/` or a new file referenced from this task). The audit itself reads both `lor-workspace` and `land-of-lor`'s git history — read-only, no commits needed to perform the audit itself.

## Docs to read first

`overview.md`'s "FIRST TASK" section in full (the complete scope, stakes, and the visibility recommendation this gates) — nothing else needed to execute this cold.

### workspace-dashboard/002 — Blocker/question aggregation queue
```yaml
id: workspace-dashboard/002
title: Blocker/question aggregation queue
epic: workspace-dashboard
state: done
priority: 2
blocked_by: ["workspace-dashboard/001"]
estimate: M
created: 2026-08-05
updated: 2026-08-12
claimed_by: null
claimed_at: null
delivers: []
review_artifact: https://lor.kris.ai-task-runner.com/now
```

## What to do

Build the "what needs Kris?" queue — the dashboard's core feature per `overview.md`'s constraint 5. **Note: this task's scope shrank significantly during this same session** — the "exact structured format for agent-written blockers/questions" that `overview.md` originally listed as an open question is now answered by the project-wide Question object (`epics/<epic-id>/questions/NNN-slug.md`, see `epics/README.md`), built for other reasons this session. This task is now "build a view over the existing mechanism," not "invent a new format."

**Scope addition from Kris (2026-08-04), verbatim intent:** the dashboard must answer, at a glance, *"are there any tasks ready for AI agents to work on, and if not, what are the blockers?"* — he asked an agent exactly this question today and the answer required grepping every epic's `status.md` by hand. So the queue is not only open questions: it must also surface, across all epics, anything `agent-ready`, anything in `review` waiting on Kris, and anything `needs-human-decision` — i.e. the tag/next fields that already exist, aggregated into one view.

## Definition of done

A view (can start as the already-built `/questions` site page) that aggregates every `status: open` question across every epic — **plus** the at-a-glance answer to "what's agent-ready / what's waiting on Kris" described above (epic tags + `next` lines aggregated, not just questions).

## Built 2026-08-04 — in review

Shipped as **`/now`** (first item in the site nav; `/questions` redirects there; `/questions/index.json` unchanged). Sections in queue order: Waiting on my review (tasks in `review`, each with its `review_artifact` path as copyable text) → Open decisions → Ready for agents → Blocked-and-why (epic `next:` lines). Supporting changes shipped alongside: new derived epic tag `needs-review` (a task in review no longer hides an epic as "blocked"), new optional task field `review_artifact:`, gold "needs Kris" chips on the epic board, epic `next:` lines on the index cards, and a build timestamp in the footer (stale stamp = dead deploy watcher). Prioritisation rule used (assumption — the `Q001` this file used to reference was never actually created): review first, then questions oldest-first, then ready, then blocked. **Kris reviews the live page and moves this to done if it answers his question.**

## Where the work lands

`site/` in this workspace repo.

## Docs to read first

`epics/README.md` (the Question schema); the site's existing `/questions` page, if built by the time this task is picked up.

### workspace-dashboard/003 — Authenticated control surface, separate from the public static build
```yaml
id: workspace-dashboard/003
title: Authenticated control surface, separate from the public static build
epic: workspace-dashboard
state: backlog
priority: 2
blocked_by: ["workspace-dashboard/001"]
estimate: L
created: 2026-08-05
updated: 2026-08-05
claimed_by: null
claimed_at: null
delivers: []
review_artifact: null
```

## What to do

Build the authenticated surface for submitting tasks, editing status, and triggering agent runs — per `overview.md`'s constraint 1 (split read from write). Must be genuinely server-side auth; the current site is a static bundle with no such thing.

## Definition of done

A working control surface reachable only by Kris, entirely separate from the public static site. Default assumption for auth unless something concrete argues otherwise: GitHub OAuth restricted to Kris's own account — no new credential to create or leak, and GitHub is already this project's identity anchor. State whatever assumption is actually made explicitly.

## Where the work lands

`site/` in this workspace repo, likely needing something beyond static hosting.

## Docs to read first

`overview.md` constraints 1 and 3 (split read/write; files stay canonical — this surface still reads/writes the same markdown, it doesn't become a second data store).

> **Superseded (2026-09-03):** this intent now lives in `building-the-game` (sign-in: 003; agent kickoff: 005). Left in backlog as a record.

> **Superseded (2026-09-03):** this intent now lives in `building-the-game` (sign-in: 003; agent kickoff: 005). Left in backlog as a record.

### workspace-dashboard/004 — Kick off an agent session against an epic, from the dashboard
```yaml
id: workspace-dashboard/004
title: Kick off an agent session against an epic, from the dashboard
epic: workspace-dashboard
state: backlog
priority: 3
blocked_by: ["workspace-dashboard/001","workspace-dashboard/003"]
estimate: M
created: 2026-08-05
updated: 2026-08-05
claimed_by: null
claimed_at: null
delivers: []
review_artifact: null
```

## What to do

Wire a button/action on the (authenticated) dashboard that submits a task-runner job equivalent to "pick up epic X and proceed" (per `epics/PICKUP.md`), so Kris doesn't have to leave the dashboard to start a session.

## Definition of done

Clicking an epic on the authenticated control surface starts a real agent session against it, using the same pick-up protocol any manually-typed instruction would trigger.

## Where the work lands

`site/` in this workspace repo, or wherever the authenticated control surface (task 003) ends up living.

## Docs to read first

`epics/PICKUP.md`; task 003 (this depends on the control surface existing first).

> **Superseded (2026-09-03):** this intent now lives in `building-the-game` (sign-in: 003; agent kickoff: 005). Left in backlog as a record.

> **Superseded (2026-09-03):** this intent now lives in `building-the-game` (sign-in: 003; agent kickoff: 005). Left in backlog as a record.

### workspace-dashboard/005 — Public-content intake path (never direct task input)
```yaml
id: workspace-dashboard/005
title: Public-content intake path (never direct task input)
epic: workspace-dashboard
state: backlog
priority: 3
blocked_by: ["workspace-dashboard/001"]
estimate: M
created: 2026-08-05
updated: 2026-08-05
claimed_by: null
claimed_at: null
delivers: []
review_artifact: null
```

## What to do

Build the intake path for public suggestions per `overview.md`'s constraint 2 — a form that writes a markdown file, or similar, landing as **triage material Kris reviews by hand**. There must be no automatic path from public input to an agent acting on it. (GitHub Discussions and outside PRs are off the table: all lor repos are permanently private per the 2026-08-04 decision, `../questions/001-repo-visibility.md` — giscus/Discussions require a public repo.)

## Definition of done

A working intake mechanism (form-to-file or similar) that lands content somewhere Kris reviews and manually promotes into an epic/task/question if warranted — never anywhere an agent reads it as a task prompt automatically.

## Where the work lands

`site/` in this workspace repo, plus whatever the intake mechanism needs (which cannot be GitHub Discussions — private repo).

## Docs to read first

`overview.md` constraint 2 in full — the reasoning (prompt-injection risk, hostile-commit failure mode) matters as much as the mechanism.

## questions/ (1)

### workspace-dashboard/Q001 — Do the lor repos ever go public?
```yaml
id: workspace-dashboard/Q001
title: Do the lor repos ever go public?
epic: workspace-dashboard
blocks: []
status: answered
raised: 2026-08-04
answered: 2026-08-04T00:00:00.000Z
decision_ref: null
```

Should any of the lor repos be made public — specifically, should `lor-workspace` (the repo behind the public site) open up now that the secrets audit came back clean?

This cleared the question bar on two counts: it's public-facing, and it's effectively irreversible (opening a repo later is trivial; un-publishing one doesn't undo the exposure that already happened).

## Options

1. **Split visibility** — `lor-workspace` public, `land-of-lor` private. This was the recommendation on record in `overview.md` while the secrets audit gated it: the workspace is the public-facing story, the game repo holds signing material and unfinished design work.
2. **Everything public** — full build-in-public, source included.
3. **Everything private** — no public lor repos at all; the site (`site/`'s build output) remains the *only* public surface. Possibly publish a separate, clean template repo later so others can reuse the workspace/agent-board mechanism without any lor content in it.

## Recommendation

The standing agent recommendation was option 1 (split visibility), gated on the audit. The audit came back clean (see `../001-secrets-audit-findings.md`), so option 1 was available.

## Decision (2026-08-04)

**Kris chose option 3: everything private.** All lor repos — `lor-workspace` included — stay private; the site is the sole public window. He may at some point generate a **template repo** for others to use (the mechanism without the lor content), but lor itself will not provide public repos.

Carried out same day: `lor-elementals`, which was the one lor repo actually public on GitHub at decision time, was flipped to private (Kris approved explicitly) — all four lor repos are now private.

Knock-ons recorded where they live:
- Giscus/GitHub Discussions can never be enabled on this repo (giscus requires a public repo) — noted in `epics/public-site-and-comms/`.
- Public-content intake (task 005) loses the GitHub Discussion and outside-PR options; only form-to-file-style intake survives.
- The site's `/press` "Source repository" link and `/workshop` repo-visibility wording updated to match.

(`decisions/NNNN-slug.md` doesn't exist as a mechanism yet per `epics/README.md` — this Decision section is the record until it does, same pattern as `epics/naming-and-brand/questions/001-studio-name.md`.)

## deliverables (0)
