feat: read the real gitea backlog into the app (P1 thin slice)
The app now displays its own live backlog instead of fixtures. First end of the sync loop — the tap-root (#1) grows list reads and a read path through the Electron main process. - @commitea/core: client gains `listIssues` (paginated, PRs excluded) and `listMilestones`; a `lifecycle-v0` mapper (closed→done, labelled/milestoned→triage, bare→diagnosis — steeping/review await event inference in P1-5). +10 unit tests. - main: gitea bridge over IPC (token stays in main, never the renderer); config from env / .env.local; gated off under COMMITEA_E2E so the committed e2e stays on fixtures. Preload exposes the typed bridge. - renderer: useBacklog() reconciles once on mount; issuesToBoardColumns shapes real issues into The pot. Board takes optional real columns + a loading state, falling back to demo fixtures when unconfigured. Verified: 14 e2e green (fixture mode) + a gated live spec that launches against the real repo — the board renders the actual 25 open + 9 closed issues (screenshot). SQLite mirror + reconcile-on-a-timer + lifecycle event inference are the next slices (#2/#3/#5). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
63
apps/desktop/src/main/gitea.ts
Normal file
63
apps/desktop/src/main/gitea.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Main-process gitea bridge. All gitea traffic runs here — the token never
|
||||
* reaches the renderer (which is CSP-locked to 'self' anyway). The renderer
|
||||
* calls these over IPC (see preload). Config for the dogfood slice comes from
|
||||
* the environment or the repo's .env.local; Settings/Onboarding wire it up
|
||||
* properly later.
|
||||
*/
|
||||
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
|
||||
import { createGiteaClient, type GiteaConfig } from '@commitea/core'
|
||||
import { ipcMain } from 'electron'
|
||||
|
||||
/** Walk up from cwd looking for a .env.local with a GITEA_TOKEN (dev convenience). */
|
||||
function loadEnvLocalToken(): string | undefined {
|
||||
let dir = process.cwd()
|
||||
for (let i = 0; i < 6; i++) {
|
||||
try {
|
||||
const txt = readFileSync(join(dir, '.env.local'), 'utf8')
|
||||
const m = /^GITEA_TOKEN\s*=\s*(.+?)\s*$/m.exec(txt)
|
||||
if (m) return m[1].trim()
|
||||
} catch {
|
||||
// not in this dir — keep walking up
|
||||
}
|
||||
const parent = dirname(dir)
|
||||
if (parent === dir) break
|
||||
dir = parent
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function resolveConfig(): GiteaConfig | null {
|
||||
// E2E runs against fixtures — never hit the network from the test harness.
|
||||
if (process.env.COMMITEA_E2E === '1') return null
|
||||
const token = process.env.GITEA_TOKEN ?? loadEnvLocalToken()
|
||||
if (!token) return null
|
||||
return {
|
||||
baseUrl: process.env.GITEA_BASE_URL ?? 'https://gitea.stephenmann.io',
|
||||
token,
|
||||
owner: process.env.GITEA_OWNER ?? 'christian',
|
||||
repo: process.env.GITEA_REPO ?? 'commitea',
|
||||
}
|
||||
}
|
||||
|
||||
export function registerGiteaIpc(): void {
|
||||
const config = resolveConfig()
|
||||
const client = config ? createGiteaClient(config, fetch) : null
|
||||
const repo = config ? `${config.owner}/${config.repo}` : null
|
||||
|
||||
ipcMain.handle('gitea:status', () => ({ configured: !!config, repo }))
|
||||
|
||||
ipcMain.handle('gitea:reconcile', async () => {
|
||||
if (!client) return { configured: false, issues: [], milestones: [] }
|
||||
const [issues, milestones] = await Promise.all([client.listIssues(), client.listMilestones()])
|
||||
return { configured: true, issues, milestones }
|
||||
})
|
||||
|
||||
ipcMain.handle('gitea:getIssue', async (_event, index: number) => {
|
||||
if (!client) return null
|
||||
return client.getIssue(index)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user