← Back to Skills

velais-project-dashboard

active

Creates and maintains a Velais-branded project dashboard as static .html pages generated from .md files in a dashboard/ folder: three shared pages committed for the whole team (Tasks with due dates, client milestones and a timeline, Resources, Handoffs with internal handoff files and client blockers) plus private, per-person profiles that stay local (Topics, Mockup, Data, Structure, Plan, session log). Use when a user asks for a project dashboard, status page, control panel or "where does the project stand" view for a Velais project; when adding tasks, milestones, resources, handoffs, decisions, open questions, data sources, phases, mockups or todos; when switching or creating a dashboard profile; or when a Claude session must log its summary. Do NOT use for client-facing React apps (use velais-ui-design), for BC-Delivery vault control planes with write-back, or for one-off charts.

Owner: velais Category: workflow-automation Version: 2.1.0 Tokens: ~28k
dashboardproject-managementmarkdownstatic-sitecollaborationprofilesvelais
94

Quality Score Breakdown

Structure (15%) 18/18
Show checks (11)
  • ✓ SKILL.md exists with exact casing 3/3
  • ✓ Valid YAML frontmatter 3/3
  • ✓ No unexpected frontmatter keys 1/1
  • ✓ Name field valid (kebab-case) 2/2
  • ✓ Name matches folder name 1/1
  • ✓ Description field present 2/2
  • ✓ No angle brackets in frontmatter 1/1
  • ✓ Folder name is kebab-case 1/1
  • ✓ No README.md inside skill folder 1/1
  • ✓ Test directory with test-cases.yml exists 2/2
  • ✓ Status 'active' is valid 1/1
Description (20%) 20/22
Show checks (7)
  • ✓ Contains action verbs: creates 2/4
  • ✓ Contains trigger indicators: use when, use for, asks for 5/5
  • ✓ Description is specific and actionable 4/4
  • ✓ File types mentioned in description 3/3
  • ✓ Description length: 895/1024 chars 2/2
  • ✓ Has negative triggers (scope boundaries) 2/2
  • ✓ Owner/author specified in metadata 2/2
Instructions (25%) 24/28
Show checks (8)
  • ✓ Skill body has content 3/3
  • ✓ Has step/section structure 4/4
  • ✓ Includes examples 5/5
  • ✓ Includes error handling 4/4
  • ✗ Consider moving detailed docs to references/ for better token efficiency 0/4
  • ✓ Actionable language: 4/10 verb patterns found 3/3
  • ✓ Word count: 2243/5000 2/2
  • ✓ All referenced paths exist 3/3
Test Coverage (25%) 29/29
Show checks (10)
  • ✓ test-cases.yml exists and parses 3/3
  • ✓ 9 should-trigger tests ✓ 4/4
  • ✓ 4 should-not-trigger tests ✓ 3/3
  • ✓ 11 functional tests ✓ 5/5
  • ✓ 5 negative tests ✓ 3/3
  • ✓ 2 edge case tests ✓ 3/3
  • ✓ Performance baseline documented 2/2
  • ✓ All functional tests have ≥2 assertions 2/2
  • ✓ All trigger phrases are diverse 2/2
  • ✓ All assertions are specific 2/2
Security (15%) 14/15
Show checks (5)
  • ✓ No secrets detected 5/5
  • ✓ No injection vectors in frontmatter 3/3
  • ✓ Name is not reserved 3/3
  • ✓ No suspicious code patterns 2/2
  • ✗ 5 external URLs found — verify they're necessary: https://fonts.googleapis.com, https://fonts.gstatic.com, https://fonts.googleapis.com/css2?family=Host+Grotesk:wght@400;500;600&display=s 1/2

Suggestions

  • → Move detailed reference material to references/ and link from SKILL.md

Test Coverage

9
Should Trigger
4
Should Not Trigger
11
Functional
5
Negative
1
Edge Cases
No
LLM Evals

Velais project dashboard

One folder, dashboard/, holds Markdown content, a stdlib Python generator, brand assets and the generated HTML. No server, no LLM in the build, no timestamps in the output. Pages are read-only: people edit the Markdown (by hand or through Claude) and the Stop hook rebuilds.

Two areas:

  • Shared (committed, same for everyone, first in the nav): Tasks, Resources, Handoffs, plus the code, the assets and the handoffs/ folder at the repo root.
  • Private (gitignored, one profile per person, after a divider in the nav): Topics, Mockup, Data, Structure, Plan and the session log. Fully personalizable.
dashboard/
  build.py            Markdown -> *.html (deterministic, stdlib only)     shared
  session_log.py      hook: registers each Claude session                 shared
  config.json         project, client, shared page paths                  shared
  assets/             style.css, font, logo, favicon                      shared
  content/
    tasks.md          internal tasks with due dates (Tasks page)          shared
    milestones.md     client/SoW milestones, optional (Tasks timeline)    shared
    resources.md      free Markdown (Resources page)                      shared
    handoffs.md       client blockers only (Handoffs page, part b)        shared
  profiles/<name>/    private, gitignored
    config.json       optional {"pages": [...]}
    topics/*.md  mockup/*.html  data.md  structure.md  plan.md  sessions/<id>.md
  .profile            name of the active profile (gitignored)
  *.html              generated, gitignored; index.html redirects to tasks.html
handoffs/             one .md per internal handoff (Handoffs page, part a)  shared
  README.md           format and example, skipped by the build

Install in a project

  1. Copy assets/dashboard/ from this skill to <project>/dashboard/.
  2. Handoff files: search the repo for files or folders named *handoff*. If there is a live folder of handoffs between people, point paths.handoffs_dir in dashboard/config.json at it (relative to dashboard/) and add the frontmatter below to its files. Otherwise copy assets/handoffs/ to <project>/handoffs/.
  3. Merge assets/hooks.json into <project>/.claude/settings.json (UserPromptSubmit and Stop hooks).
  4. Edit dashboard/config.json: project, client. Paths default to content/tasks.md, content/milestones.md, content/resources.md, content/handoffs.md and ../handoffs.
  5. One list: if the project has a project-management.html (or another to-do page), move its open tasks to content/tasks.md and its client or SoW dates to content/milestones.md, then delete it (git rm). Repoint any CLAUDE.md, bot or agent instruction that named it to dashboard/content/tasks.md.
  6. Create the user’s own profile: copy assets/profile/ to dashboard/profiles/<their-name>/, then fill or delete its files. Create other people’s profiles only as empty folders (mkdir dashboard/profiles/<name>).
  7. python3 dashboard/build.py --profile <their-name>, open dashboard/index.html.
  8. Add to the project CLAUDE.md: the dashboard is the only task list and bots and agents read and update dashboard/content/tasks.md; what is shared vs private; the content rules below; whose profiles must not be filled; and that sessions fill ## Summary before closing substantial work.
  9. Commit only the shared files, including handoffs/. The shipped .gitignore already excludes *.html, .profile, profiles/ and sessions/.

Keep build.py identical across projects; project content lives in Markdown. If a page needs a layout the generator cannot express, add a mockup fragment. If you do change the generator, update the copy in this skill too and keep --demo green.

Profiles

  • dashboard/.profile names the active profile; build.py --profile <name> switches and rebuilds; --profile "" shows only the shared pages. An unknown name fails with the list of existing profiles.
  • Without a profile config.json, a private page appears only when its file exists, so an empty profile shows just the shared pages. A config.json with pages fixes the list and order.
  • The header shows project · client · Profile. Switching removes the other profile’s generated pages.
  • A profile belongs to its person. Never write content into someone else’s profile, not even a helpful seed; they decide what goes there.
  • Sessions are logged in the profile active when the session started and stay there if the profile is switched mid-session. A new session started while someone else’s profile is active logs into that profile: switch back first.

Shared pages

Tasks (content/tasks.md, content/milestones.md). tasks.md holds internal due dates; milestones.md holds client or SoW dates (contract phases, client inputs, payments). Never repeat a milestone as a tasks.md checkpoint.

## Tuesday · due 2026-10-13 · @name
- [ ] Define the evaluation metrics
  - [ ] Draft the golden set

## Review checkpoint · due 2026-10-20 · @name @other

Each ## group is a dated unit with optional @owners; a group without tasks is a checkpoint. [x] and [~] still render, but done and irrelevant tasks are deleted (see Content rules).

## [x] Kick-off · due 2026-10-05
## Evaluation dataset approval · due 2026-10-23..2026-10-30
Otherwise our dataset is used
## Delivery · due 2026-12-04
€10,275 (30%)

In milestones.md, [x] marks a reached milestone, due A..B is a window (drawn as a bar), and one optional note line goes under the heading (shown in the marker’s tooltip). Headings without due are ignored. A missing file, or one without ## headings, means no milestone row and no milestone KPI: right for projects without fixed SoW dates.

The page has no title block: under the header (project · client · profile) it opens on the KPI strip (next due with countdown, next milestone with countdown when there are milestones, overdue, due in 7 days, tasks done), then the timeline with month marks and a today line. Milestones sit on their own row above the internal groups as diamonds in a separate colour (green when reached, grey when passed without [x]). Internal groups get one marker each (circle = tasks, diamond = checkpoint; colour = on track, due within 3 days, overdue, done). There is no card list under the timeline: hovering, focusing (Tab) or tapping a marker opens a tooltip with the name, the date or window and the note (for groups: date, owners and n/m done); Escape or a second tap closes it. Then come the due-date cards sorted by date with progress bars. Countdowns and the today line are computed in the browser, so the HTML stays deterministic. HTML comments in both files are ignored, so the format hint can live at the top.

Keep the page read-only. In-browser editing (local server + PUT) was tried and dropped: it adds a server to run and a second write path.

Handoffs, two parts:

  • (a) Internal handoffs between people: one Markdown file each in handoffs/ at the repo root (paths.handoffs_dir, relative to dashboard/, default ../handoffs), named YYYY-MM-DD-short-topic.md, with frontmatter:

    ---
    from: alex
    to: sam
    date: 2026-10-12
    title: Review notes on the first draft
    status: open
    ---

    The page lists open handoffs newest first (date, from → to, title); a click opens the handoff IN PLACE, its body rendered from Markdown (## headings, tables, nested -/1. lists with indented continuation lines, > quotes, code fences, bold, code, links), with a link to the source .md at the bottom. status: done moves one under a collapsed “Done” disclosure. README.md is skipped. No files shows a short empty state. Write a handoff only when the user asks.

  • (b) content/handoffs.md: only “Waiting on ” blockers, rendered as cards under “External blockers”.

Resources (content/resources.md). Free Markdown: an intro paragraph, then ## Section blocks with paragraphs and bullets, each rendered as a card. The intro of handoffs.md is the Handoffs page lead and its sections follow the same format. Missing or title-only file shows an empty state. Fill them only with what the user gives you. Resources is a page, not a folder: never copy, move or symlink client material (databases, code copies) into dashboard/; reference it by its workspace path.

Content rules

  • Tasks: about 3–4 real tasks per person per week, similar ones merged. No chasing or confirmation filler, no “if it comes up” items, no sub-project prefixes like [GL] / [DPR].
  • Done tasks are deleted from tasks.md, not ticked and kept. Tasks that no longer matter are deleted, not ticked.
  • Client or SoW dates go in milestones.md, internal due dates in tasks.md; never both.
  • content/handoffs.md: only real external blockers from the client, one short section. No “what we owe them”, no internal notes, nothing that is already a task.
  • One list: the dashboard replaces any project-management.html; delete it on install. Bots and agents read and update content/tasks.md.

Private pages (inside the active profile)

Write all content in English, even when the chat is in another language.

Topics (topics/<topic>.md): # Title, one scope line, one bullet per claim:

- [deciso] Exclude the client's current roster. — source: chat decision 2026-10-07
- [aperto] Does an exclusive deal block? (conflicts with the Armada case) — source: call 2026-10-05

Tags are fixed identifiers: deciso (decided with the client), stakeholder (said by the client, not decided), proposta (proposed), aperto (open). (conflicts with …) lands in Needs attention, (stakeholder answer: …) or (resolved: …) closes a question, (superseded …) hides a claim. Never delete a claim. README.md in the folder is always skipped, so it can document the format with examples.

Data (data.md): ## Source, then status: (available, partial, blocked, unverified), optional verified: YYYY-MM-DD, owner: @name, access: …, a free paragraph, bullets. Build it from the real sources, not from memory or docs:

  • Query databases read-only with an absolute URI (sqlite3.connect("file:/abs/path.sqlite?mode=ro", uri=True)). A relative path from the wrong directory silently creates an empty database file.
  • One section per data domain (documents, templates, master data, permissions, …) with real counts and distributions, plus sections for the code that reads them (source registry, API routes, schema generation, SQL scripts). Note how code joins the data when it matters (e.g. a join on a text name instead of an id).
  • Recount before writing a number; state masked values as masked.
  • Surface surprises as Open: in the session summary, not as decisions.

Structure (structure.md): intro, then ## Phase sections in flow order: phase question, ### Input, ### Rules, ### Output. A phase named with “gate” or “eligib” renders as a hard gate; optional ## Loop becomes the feedback box. Each phase also gets its own page.

Plan (plan.md): ## Lane, optional description, GitLab task lists. [ ] open, [x] closed, [~] n/a; #id on top-level todos only, next id = highest + 1, never reused; @name, · opened, · closed, · due YYYY-MM-DD. Change it only when the user explicitly asks, and only the items they name.

Mockup (mockup/<name>.html): one HTML fragment per tab, first line <!-- title: Tab name -->, shared classes only (hero, card, grid2, kpis, kpi, tag, label, muted, tile, masonry), no inline colours, no scripts, placeholder data only.

Sessions (sessions/<id>.md): created by the hook with author from git config user.name. Before closing substantial work, replace the ## Summary placeholder with 2–5 bullets prefixed Decided: / Open: / Ingested / Researched where they apply.

Working in a team

  • Commit shared files only. Generated HTML is gitignored, so there are no HTML merge conflicts; after a pull, run python3 dashboard/build.py.
  • Decisions enter Topics only after the user confirms them; stakeholder statements get stakeholder. Conflicts between sources are shown, never resolved silently.
  • No personal data: people by name and role, never emails or phones.

Checks

  • python3 dashboard/build.py --demo and python3 dashboard/session_log.py --demo print ok; run both after any script change. The demo builds in a temp copy that excludes content/, profiles/, sessions/ and .profile, so real content never skews it; it covers missing and empty milestones.md, milestones and windows on the timeline, and handoff files (open listed newest first, done collapsed, README skipped).
  • A build error never blocks the hook (stderr, exit 0). If a page is missing, run build.py by hand and read the error; check .profile names an existing profile.
  • A claim that does not appear: tag spelling, the em dash in — source:, the line starts with - [, and the file is not README.md.
  • A milestone that does not appear: the heading needs · due YYYY-MM-DD and must not sit inside an HTML comment. A handoff that does not appear: the file ends in .md, is not README.md, starts with --- frontmatter, and is not status: done.

Common mistakes

MistakeFix
Client or SoW dates as tasks.md checkpointsPut them in milestones.md; tasks.md keeps internal due dates
Ticked done items kept in tasks.mdDelete done and irrelevant tasks
Filler tasks, “if it comes up” items, [GL]/[DPR] prefixes3–4 real tasks per person per week, similar ones merged
”We owe them”, internal notes or tasks in handoffs.mdClient blockers only; internal handoffs are files in handoffs/
project-management.html left next to the dashboardMove its content, delete it; one list in content/tasks.md
Seeding a colleague’s empty profileCreate the folder empty; it is theirs
Copying or symlinking client data into dashboard/resourcesResources is a page; reference workspace paths
Hardcoding page content in build.pyPut it in Markdown or a mockup fragment
New module-level name in build.py shadowing an existing one (a TASK regex broke the Plan parser)Prefix new globals per feature (TASK_LINE, TASKS_CSS) and run --demo
Demo fixture built from the real content/Keep the demo’s ignore list; fixtures live in the temp copy
sqlite3.connect("x.sqlite") from the wrong cwdRead-only absolute URI
$Q "select …" in zsh passing the whole string as one wordUse Python, or a shell function, for repeated queries
Adding todos the user did not ask forPlan changes only on explicit request
Build timestamps or random ids in outputKeep output deterministic; dates relative to today go in browser JS