diff --git a/.env.example b/.env.example index 4d10e4e..d161976 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/README.md b/README.md index dbabb89..ec48c9a 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 | diff --git a/package.json b/package.json index 358ecf1..2e6ae07 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/server/index.ts b/server/index.ts index f4bf088..e196e98 100644 --- a/server/index.ts +++ b/server/index.ts @@ -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, diff --git a/src/App.tsx b/src/App.tsx index df9957b..2d7f6ea 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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. */ diff --git a/src/GuideView.tsx b/src/GuideView.tsx index 9bc59f6..9907c0d 100644 --- a/src/GuideView.tsx +++ b/src/GuideView.tsx @@ -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[]; @@ -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.' }, @@ -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.' }, @@ -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'