Skip to content
Open
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
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,27 @@
# Required. GitHub PAT with `repo` scope (read access to the orgs you track).
GITHUB_TOKEN=

# The GitHub organization(s) you track, comma-separated — e.g. your-org, another-org.
# Read by BOTH the server and the UI, so what the settings panel suggests is what
# gets queried. Leave blank and you name the org on each project instead; the PR
# fetch then says so rather than showing an empty queue.
VITE_GITHUB_ORGS=

# Optional. Needed only to read Slack channel signals and to post a checkpoint
# draft to a channel. Slack bot token (xoxb-…) with channels:history + chat:write.
SLACK_BOT_TOKEN=

# Optional. Needed only for STREAM ATTRIBUTION — grouping a project's PRs by
# workstream, resolved from each PR's Jira ticket and the swimlane its board puts
# that ticket in. Leave blank and the PR list still works; every PR simply lands
# unattributed. Set all three or none.
# JIRA_BASE_URL your site, e.g. https://yourcompany.atlassian.net
# JIRA_EMAIL the account the API token belongs to
# JIRA_API_TOKEN from https://id.atlassian.com/manage-profile/security/api-tokens
JIRA_BASE_URL=
JIRA_EMAIL=
JIRA_API_TOKEN=

# --- shell -------------------------------------------------------------------
# Port for the API server (the Vite dev proxy expects 4320).
PORT=4320
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
Your own dashboard, running only the sections you install. Nothing here reads
anyone else's data: your notes live in this folder, and the tokens are yours.

Four sections are wired up out of the box:
Wired up out of the box, grouped into **Today**, **Archives** and **Guide**:

| Tab | What it does | Reads / writes |
|---|---|---|
| 🏠 Home | Empty page, yours to fill | — |
| 🔀 Pull requests | Per-squad PR checkpoints from GitHub, Slack channel signals, and a ready-to-paste Slack draft | `.data/` |
| ✅ Tasks | Eisenhower-quadrant backlog + a Today block | `content/tasks/active.md` |
| 📓 Journal | Per-day journal | `content/tasks/journal.md` |
| 📅 Meetings | Meeting notes by bucket, with live action-item checkboxes and archiving | `content/meetings/` |
| ☀️ Today ▸ Day | The cockpit: backlog size, what you owe from meetings, and a panel listing every Claude Code session you have open | reads `content/` (counts); the sessions panel reads `~/.claude` |
| ✅ Today ▸ Tasks | Eisenhower-quadrant backlog + a Today block | `content/tasks/active.md` |
| 🔀 Today ▸ PRs | Per-squad PR checkpoints from GitHub, stacked-PR chains, Slack channel signals, and a ready-to-paste Slack draft | `.data/` |
| 📓 Archives ▸ Journal | Per-day journal | `content/tasks/journal.md` |
| 📅 Archives ▸ Meetings | Meeting notes by bucket, with live action-item checkboxes and archiving | `content/meetings/` |
| 📖 Guide | Setup, How to use, FAQs — describing *your* tabs | — (`src/GuideView.tsx`) |

## Setup (~2 minutes)

Expand Down Expand Up @@ -65,7 +66,7 @@ seeded files in `content/`, which double as format documentation.

## The skills

`.claude/skills/` ships eight Claude Code skills. These five keep the dashboard's
`.claude/skills/` ships ten Claude Code skills. These five keep the dashboard's
data current, so you're not hand-editing markdown:

| Skill | What it does |
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
"@asucregonzalez/section-claude-sessions": "^0.1.0",
"@asucregonzalez/section-journal": "^0.1.2",
"@asucregonzalez/section-meetings": "^0.2.0",
"@asucregonzalez/section-pull-requests": "^0.3.0",
"@asucregonzalez/section-tasks": "^0.2.0",
"@asucregonzalez/section-pull-requests": "^0.4.0",
"@asucregonzalez/section-tasks": "^0.3.0",
"@asucregonzalez/theme": "^0.2.0",
"@asucregonzalez/ui": "^0.2.0",
"@asucregonzalez/ui": "^0.3.0",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"react": "^18.3.1",
Expand Down
18 changes: 17 additions & 1 deletion server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,24 @@ console.log(`[command-center] content root: ${ctx.contentRoot}`);
// Pull requests starts with no projects; add your squads from its own UI. To
// pre-seed them in code, pass { seedProjects, seedSquads, displayNames } as a
// third argument — written to .data on first run only.
//
// `defaultOrganizations` is the GitHub org(s) a project falls back to when it
// names none. The package deliberately ships none: an org baked into a shared
// package doesn't fail visibly, it quietly queries someone else's GitHub and
// returns their PRs. Read from the same VITE_GITHUB_ORGS that src/App.tsx uses,
// so the org the settings UI suggests is the org this server actually queries.
// Leave it unset and a PR fetch says "no organizations configured" rather than
// looking like an empty queue.
const githubOrgs = (process.env.VITE_GITHUB_ORGS ?? '')
.split(',')
.map((o) => o.trim())
.filter(Boolean);

const prRouter = express.Router();
registerPullRequestsRoutes(prRouter, ctx, { defaultOrganizations: githubOrgs });
app.use(prRouter);

const routers = [
registerPullRequestsRoutes,
registerTasksRoutes,
registerJournalRoutes,
registerMeetingsRoutes,
Expand Down
9 changes: 9 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@ import { sections } from './sections';
* VITE_OWNER_NAME in .env.
* - refreshEnabled: false because this app serves no /api/refresh route, so
* sections hide their "Regenerate" buttons instead of offering a dead one.
* - defaultOrganizations: the GitHub orgs you track, from VITE_GITHUB_ORGS in
* .env. The PR section ships with none — it must not guess an org — so a new
* project is prefilled from this, and with it unset you name the org yourself
* when you create one. `server/index.ts` reads the SAME variable, so the org
* the UI suggests is always the org the server queries.
* Module-level constant on purpose: its identity must stay stable across renders.
*/
const DASHBOARD_CONFIG: DashboardConfig = {
ownerName: import.meta.env.VITE_OWNER_NAME as string | undefined,
refreshEnabled: false,
defaultOrganizations: ((import.meta.env.VITE_GITHUB_ORGS as string | undefined) ?? '')
.split(',')
.map((o) => o.trim())
.filter(Boolean),
};

/** First child of a group, or the entry itself when it has none. */
Expand Down
25 changes: 19 additions & 6 deletions src/GuideView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,17 @@ type Kind = 'in-app' | 'chat' | 'both';

interface Item { kind: Kind; text: string }
interface Card {
/** Matches a section id in `sections.ts`, or `null` for a concept card. */
section: string | null;
/**
* The section id(s) in `sections.ts` this card documents, or `null` for a
* concept card that describes no tab.
*
* An array when one card covers several destinations. `destinations` is the
* flattened LEAF list, so a card naming a *group* id matches nothing: it is
* filtered out of the render, and the group's leaves are reported undocumented
* forever. The Guide is exactly that shape — one card describing three
* sub-tabs — which is why a card may claim more than one id.
*/
section: string | string[] | null;
icon: string;
title: string;
items: Item[];
Expand All @@ -45,7 +54,7 @@ const SETUP: Card[] = [
},
{
section: null, icon: '▶️', title: 'Start it',
pre: 'make dev # dashboard + API\nmake claude # Claude Code in this folder',
pre: 'make dev # dashboard + API\nmake run # Claude Code in this folder',
items: [
{ kind: 'both', text: 'The dashboard and its API run as two processes; `make dev` starts both and prints the port. If a port is taken it picks the next one — check the line it prints rather than assuming 5173.' },
{ kind: 'both', text: 'A server-side change needs the API restarted; client changes hot-reload. If an edit seems to do nothing, that is the first thing to check.' },
Expand Down Expand Up @@ -118,7 +127,7 @@ const USAGE: Card[] = [
],
},
{
section: 'guide', icon: '📖', title: 'Guide (this tab)',
section: ['setup', 'how-to', 'faq'], icon: '📖', title: 'Guide (this tab)',
items: [
{ kind: 'in-app', text: 'Three parts: Setup for first-run, How to use for what each tab does, FAQs for what goes wrong. Cards start collapsed — the count on the right is how many notes are inside.' },
{ kind: 'both', text: 'Its content lives in src/GuideView.tsx, not in a package, because it describes YOUR tabs. Add or remove a section and edit the cards here.' },
Expand Down Expand Up @@ -169,8 +178,12 @@ export function GuideView({ part = 'usage' }: { part?: Part } = {}) {
// sections.ts: a card for a section you removed is hidden, and a section with no
// card is named at the bottom instead of going undocumented in silence.
const installedIds = new Set(destinations.map((s) => s.id));
const cards = active.cards.filter((c) => c.section === null || installedIds.has(c.section));
const documented = new Set(active.cards.map((c) => c.section).filter(Boolean) as string[]);
const claimed = (c: Card): string[] =>
c.section === null ? [] : Array.isArray(c.section) ? c.section : [c.section];
const cards = active.cards.filter(
(c) => c.section === null || claimed(c).some((id) => installedIds.has(id)),
);
const documented = new Set(active.cards.flatMap(claimed));
// Counted against leaf destinations so a group heading never reads as an
// undocumented section. Only shown on How to use, which is where the cards are.
const missing = part === 'usage'
Expand Down