Rough edges: mobile 100dvh, session-expiry in chat, sticky error toasts, journal date filter (#93)
Build image / build-and-push (push) Successful in 11s
Build image / build-and-push (push) Successful in 11s
Part of #85. 100vh→100dvh on both full-height pages (editor + public), a 401 during chat now redirects to /login instead of mislabelling a dead session as "assistant broken", error toasts persist (capped at 4) with a close button, and the journal's built-but-unreachable date filter gets a UI. Gadfly round handled (public-page 100dvh, toast cap, shared date-input class). Larger deferred items (revision retention, agent toolbox gaps) noted for their own issues.
This commit was merged in pull request #93.
This commit is contained in:
@@ -17,9 +17,16 @@ interface ToastState {
|
||||
|
||||
let nextId = 1
|
||||
|
||||
// Error toasts no longer auto-dismiss (#85), so a burst of failures could grow
|
||||
// the stack without bound and push older ones off-screen. Cap it: keep the most
|
||||
// recent MAX_TOASTS and drop the oldest, so the newest — the one that just
|
||||
// happened — is always visible.
|
||||
const MAX_TOASTS = 4
|
||||
|
||||
export const useToastStore = create<ToastState>((set) => ({
|
||||
toasts: [],
|
||||
push: (message, tone = 'info') => set((s) => ({ toasts: [...s.toasts, { id: nextId++, message, tone }] })),
|
||||
push: (message, tone = 'info') =>
|
||||
set((s) => ({ toasts: [...s.toasts, { id: nextId++, message, tone }].slice(-MAX_TOASTS) })),
|
||||
dismiss: (id) => set((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) })),
|
||||
}))
|
||||
|
||||
@@ -32,21 +39,34 @@ export const toast = {
|
||||
// Param is `item`, not `toast`, so it doesn't shadow the module's `toast` export.
|
||||
function ToastItem({ item }: { item: Toast }) {
|
||||
const dismiss = useToastStore((s) => s.dismiss)
|
||||
const isError = item.tone === 'error'
|
||||
useEffect(() => {
|
||||
// Error toasts are the primary report that a mutation failed, so they do NOT
|
||||
// auto-dismiss — a user who looked away at second 4 would otherwise lose the
|
||||
// only notice, with nothing to retrieve (#85). Info toasts still time out.
|
||||
if (isError) return
|
||||
const t = setTimeout(() => dismiss(item.id), 4000)
|
||||
return () => clearTimeout(t)
|
||||
}, [item.id, dismiss])
|
||||
}, [item.id, dismiss, isError])
|
||||
return (
|
||||
<div
|
||||
role={item.tone === 'error' ? 'alert' : 'status'}
|
||||
role={isError ? 'alert' : 'status'}
|
||||
className={cn(
|
||||
'pointer-events-auto rounded-md border px-3 py-2 text-sm shadow-md',
|
||||
item.tone === 'error'
|
||||
'pointer-events-auto flex items-start gap-2 rounded-md border px-3 py-2 text-sm shadow-md',
|
||||
isError
|
||||
? 'border-red-500/40 bg-red-500/10 text-red-700 dark:text-red-300'
|
||||
: 'border-border bg-surface text-fg',
|
||||
)}
|
||||
>
|
||||
{item.message}
|
||||
<span className="flex-1">{item.message}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => dismiss(item.id)}
|
||||
aria-label="Dismiss"
|
||||
className="-mr-1 shrink-0 rounded px-1 text-current opacity-60 outline-none hover:opacity-100 focus-visible:ring-2 focus-visible:ring-current/40"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user