diff --git a/apps/desktop/src/main/gitea.ts b/apps/desktop/src/main/gitea.ts index d75b37b..f66216c 100644 --- a/apps/desktop/src/main/gitea.ts +++ b/apps/desktop/src/main/gitea.ts @@ -365,7 +365,23 @@ export function registerGiteaIpc(): void { }) // ---- config (team onboarding) ---- - ipcMain.handle('config:get', () => publicConfig()) + // The saved config's public view, or — when running off a .env.local / env + // fallback (dev) — a public view of the *resolved* connection, so Settings and + // the rail reflect what the app is actually connected to, not just the store. + ipcMain.handle('config:get', () => { + const saved = publicConfig() + if (saved) return saved + const c = resolveConfig() + if (!c) return null + return { + baseUrl: c.baseUrl, + owner: c.owner, + repo: c.repo, + pmStateRepo: pmStateRepoName(c), + modelUrl: process.env.MODEL_BASE_URL ?? undefined, + hasToken: !!c.token, + } + }) ipcMain.handle('config:set', (_event, cfg: AppConfig) => { saveConfig(cfg) diff --git a/apps/desktop/src/renderer/src/components/screens/capture-screen.tsx b/apps/desktop/src/renderer/src/components/screens/capture-screen.tsx index 9ac3229..d80eecb 100644 --- a/apps/desktop/src/renderer/src/components/screens/capture-screen.tsx +++ b/apps/desktop/src/renderer/src/components/screens/capture-screen.tsx @@ -24,10 +24,7 @@ interface Question { export function CaptureScreen({ onDone }: { onDone: () => void }) { const [stage, setStage] = React.useState<'dump' | 'interview' | 'review' | 'filed'>('dump') - const [dump, setDump] = React.useState( - 'auth is flaky — token refresh dies silently, sometimes session storage goes stale. ' + - 'also the webhook debounce thing keeps double-firing. and we owe docs for auth setup' - ) + const [dump, setDump] = React.useState('') const [qi, setQi] = React.useState(0) const [log, setLog] = React.useState<{ q: string; a: string }[]>([]) const [split, setSplit] = React.useState(null) diff --git a/apps/desktop/src/renderer/src/components/screens/focus-screen.tsx b/apps/desktop/src/renderer/src/components/screens/focus-screen.tsx index 6798486..0d35f05 100644 --- a/apps/desktop/src/renderer/src/components/screens/focus-screen.tsx +++ b/apps/desktop/src/renderer/src/components/screens/focus-screen.tsx @@ -7,9 +7,10 @@ import { EmptyState } from '../shell/states.js' import { Badge, Button, Card, IconButton, Tag } from '../ui/index.js' /** - * Morning service — the Now/Next/Later focus cards + the burn-up cone. - * `focus` (scheduler) and `forecast` (Monte Carlo) override the demo fixtures - * when gitea is configured; both fall back to the handoff demo otherwise. + * Morning service — the Now/Next/Later focus cards + the burn-up cone, from the + * scheduler (`focus`) and Monte Carlo forecast (`forecast`). Both come from the + * reconciled backlog; with no open work the scheduler has nothing to pour, so we + * show the empty state. */ export function FocusScreen({ onOpenIssue, @@ -66,7 +67,9 @@ export function FocusScreen({ ) - if (!focus) { + // The scheduler returns a FocusView even when the backlog has no open work — + // treat "no now/next/later" as empty, not just an absent `focus`. + if (!focus || (!focus.now && !focus.next && !focus.later)) { return ( void onDisconnect?: () => void }) { - const [webhooks, setWebhooks] = React.useState(true) - const [reconcile, setReconcile] = React.useState(true) - const [poll, setPoll] = React.useState(true) - const [nag, setNag] = React.useState(true) + const [model, setModel] = React.useState<{ configured: boolean; model: string | null } | null>(null) + + React.useEffect(() => { + let alive = true + window.commitea.model + .status() + .then((s) => { + if (alive) setModel(s) + }) + .catch(() => {}) + return () => { + alive = false + } + }, []) const Row = ({ children, style }: { children: React.ReactNode; style?: React.CSSProperties }) => (
{children}
@@ -33,128 +43,127 @@ export function SettingsScreen({

Settings

-

config lives in pm-state · versioned, portable

+

+ connection lives on this machine · the plan lives in gitea +

{connection ? ( <> - + - {connection.owner}/{connection.repo} - connected + + {connection.owner}/{connection.repo} + + + connected + - {connection.baseUrl} + + {connection.baseUrl} - sidecar: {connection.pmStateRepo ?? `${connection.repo}-pm-state`} - - - model: {connection.modelUrl ?? 'not set — chat off'} + + sidecar: {connection.pmStateRepo ?? `${connection.repo}-pm-state`} Your token is stored encrypted on this machine. Delete the sidecar and resync — no truth is lost. - - + + ) : ( Not connected. - + )}
-
- - setWebhooks(e.target.checked)} /> - endpoint :48731 · healthy +
+ + + + Reconciles on launch and after every write + - setReconcile(e.target.checked)} /> - - setPoll(e.target.checked)} /> -
- - - -
- - hot memory ≤ 2k tokens · math is never delegated to either - -

- The small one writes my standup; the large one argues with your estimates. Neither is allowed near the arithmetic. -

+ +
+ {model?.configured ? ( + + + {model.model} + + reachable + + + ) : ( + + + + Chat is off — no model reachable. + + + + )} + + An OpenAI-compatible endpoint (set it in Reconfigure). Reginald names, negotiates and explains; it never + does the arithmetic — the scheduler and Monte Carlo do that. +
- {['est/1d', 'est/2d', 'est/3d', 'est/5d', 'est/8d'].map((l) => )} + {['est/1d', 'est/2d', 'est/3d', 'est/5d', 'est/8d'].map((l) => ( + + ))} - {['p/1', 'p/2', 'p/3', 'p/4'].map((l) => )} + {['p/1', 'p/2', 'p/3', 'p/4'].map((l) => ( + + ))} Fixed sets, human-meaningful, visible in gitea. Not configurable — that is rather the point.
- -
- - Morning standup -
- -
-
-
-
-
setDark(false)} /> setDark(true)} />
- - - -
-
Forget this gitea
- Removes the connection and the local cache. Gitea itself is untouched. -
- -
-
) } diff --git a/apps/desktop/src/renderer/src/components/screens/standup-screen.tsx b/apps/desktop/src/renderer/src/components/screens/standup-screen.tsx index 533995d..41b0ace 100644 --- a/apps/desktop/src/renderer/src/components/screens/standup-screen.tsx +++ b/apps/desktop/src/renderer/src/components/screens/standup-screen.tsx @@ -161,39 +161,59 @@ export function StandupScreen({
-
onOpenIssue(NAG_REF)} - onKeyDown={(e) => { - if (e.key === 'Enter') onOpenIssue(NAG_REF) - }} - style={{ - display: 'flex', - gap: 10, - alignItems: 'flex-start', - cursor: 'pointer', - background: 'var(--warn-tint)', - borderRadius: 'var(--radius-2)', - padding: '12px 14px', - }} - > - - - -
-
- #{s.nag.id} - - steeping {s.nag.days} - - - blocks {s.nag.blocks.join(', ')} - -
-

{s.nag.text}

+ {s.nag.id === 0 ? ( + // Calm sentinel — nothing is over-steeping; render just the reassurance, + // no issue chrome, no click target (there's no #0 to open). +
+ + + +

{s.nag.text}

-
+ ) : ( +
onOpenIssue(NAG_REF)} + onKeyDown={(e) => { + if (e.key === 'Enter') onOpenIssue(NAG_REF) + }} + style={{ + display: 'flex', + gap: 10, + alignItems: 'flex-start', + cursor: 'pointer', + background: 'var(--warn-tint)', + borderRadius: 'var(--radius-2)', + padding: '12px 14px', + }} + > + + + +
+
+ #{s.nag.id} + + steeping {s.nag.days} + + + blocks {s.nag.blocks.join(', ')} + +
+

{s.nag.text}

+
+
+ )}