mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
The desktop app now keeps the composer stable while turns are active, reduces low-signal tool noise in the transcript, restores project context under the composer after session creation, and relies on the CLI's own permission requests instead of injecting broader desktop-side Bash asks. This also brings in the supporting desktop app source tree and the server routes/session metadata needed for git info, filesystem browsing, session resume, slash commands, and SDK-backed permission bridging so the UI can operate as a coherent feature instead of a partial patch. Constraint: Desktop transcript needs to stay usable during long multi-tool sessions without hiding file-change diffs Constraint: Permission prompts must mirror CLI behavior closely enough that read-only commands do not get desktop-only prompts Rejected: Keep rendering Read/Bash bodies inline | too noisy and unlike the intended transcript model Rejected: Commit only the touched desktop files | would leave the newly introduced desktop app incomplete in git history Confidence: medium Scope-risk: broad Reversibility: messy Directive: Treat non-writing tools as summary-first transcript events; do not re-expand them by default without validating the UX against long sessions Tested: cd desktop && bun run lint Tested: cd desktop && bun run test -- --run Tested: bun test src/server/__tests__/conversations.test.ts Not-tested: Manual visual regression against the exact screenshots in a live desktop session Not-tested: Full root TypeScript check (repository still has unrelated extracted-native parse failures)
48 lines
1.4 KiB
TypeScript
48 lines
1.4 KiB
TypeScript
import { useUIStore, type Toast as ToastType } from '../../stores/uiStore'
|
||
|
||
const typeStyles: Record<ToastType['type'], string> = {
|
||
success: 'border-l-4 border-l-[var(--color-success)]',
|
||
error: 'border-l-4 border-l-[var(--color-error)]',
|
||
warning: 'border-l-4 border-l-[var(--color-warning)]',
|
||
info: 'border-l-4 border-l-[var(--color-text-accent)]',
|
||
}
|
||
|
||
function ToastItem({ toast }: { toast: ToastType }) {
|
||
const removeToast = useUIStore((s) => s.removeToast)
|
||
|
||
return (
|
||
<div
|
||
className={`
|
||
bg-[var(--color-surface)] rounded-[var(--radius-md)] shadow-[var(--shadow-dropdown)]
|
||
px-4 py-3 text-sm text-[var(--color-text-primary)]
|
||
${typeStyles[toast.type]}
|
||
animate-in slide-in-from-right fade-in duration-200
|
||
`}
|
||
>
|
||
<div className="flex items-center justify-between gap-2">
|
||
<span>{toast.message}</span>
|
||
<button
|
||
onClick={() => removeToast(toast.id)}
|
||
className="text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] text-lg leading-none"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export function ToastContainer() {
|
||
const toasts = useUIStore((s) => s.toasts)
|
||
|
||
if (toasts.length === 0) return null
|
||
|
||
return (
|
||
<div className="fixed bottom-4 right-4 z-[100] flex flex-col gap-2 max-w-sm">
|
||
{toasts.map((toast) => (
|
||
<ToastItem key={toast.id} toast={toast} />
|
||
))}
|
||
</div>
|
||
)
|
||
}
|