Provider wraps the Outlet so contextual chat survives all route
transitions. The sidebar is hidden on the home route and when
chat.open is false. Sidebar size is provider-owned and persisted
to localStorage.
Top-right elevated controls (new chat, close) and a ChatSurface in
contextual variant. No header, no scope chip. Not yet mounted into
AppShell (M4.5).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pages call this in render with their current scope. Provider diffs
by JSON key and only fires the WS scope update on real change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Holds open/size/sessionId/scope/messages/streaming state. Creates
or hydrates a contextual aiChatSessions row on mount, persists
messages, and fires scope updates through window.electronAI when
the renderer scope changes. Not yet mounted into AppShell (M4.5).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend agent tools (e.g. list_tasks, count_tasks) pass multi-value
filters as a single comma-joined string like status="todo,in_progress".
The generic buildConditions matched that with eq(status, 'todo,in_progress'),
which matched zero rows — agent reported "no tasks found" for any
multi-status query. Now split on comma and emit inArray(col, parts)
when more than one part is present. Also accepts an array directly.
Repro: home chat, ask "what tasks are overdue?". Trace b09714e6e14825f5fa3a226cad053647.
Home chat now creates an aiChatSessions row on first use and
appends every user/assistant message. Session id persisted in
localStorage so reopening the app reattaches to the same row.
Hydration of past messages into the in-memory cache is deferred
to a follow-up — current visible behavior matches the previous
in-memory cache.
Critical fix: stream_end was dropping event.mutations, which the
baseline useAIChat parses into inline entity tags. Without this,
any consumer migrated from useAIChat to useChatStream would
silently lose entity card rendering on assistant messages.
Exports parseMutationsToEntityTags from useAIChat for reuse.
Also adds the missing 'stream_start' no-op switch case.
Shared between home and contextual channels. Variant prop selects
between home (full-width, fixed-bottom input) and contextual
(absolute-positioned translucent input with gradient fade) layouts.
Shared streaming engine for home (and forthcoming contextual)
channels. useAIChat still owns cache-key + tRPC dispatch; that
wiring is migrated in the next commit.
Local chat history persistence. Same model used by both home and
contextual channels. Indexes on (session_id, created_at) and
(channel, updated_at) for ordering and listing.
Typing in a segment no longer calls onChange — local state only.
onChange now fires only on commit (Enter, calendar pick), so the
parent TaskFormDialog stops re-rendering on every keystroke (and
the heavy Calendar grid + every pill / popover / query stops
re-rendering with it).
Inside DateTimeField:
- Calendar element memoized via useMemo keyed on the committed
date's ms — only re-renders when a full valid date is reached
or changes.
- SegmentSpan wrapped in React.memo.
- onSegKeyDown stabilized via useCallback + functional setSeg +
refs for order / withTime / onChange / onCommit, so its
identity never changes.
- Per-segment ref setters cached in a useRef map so they don't
swap identity on each render.
TaskFormDialog:
- onChange / onCommit passed to DateTimeField wrapped in useCallback.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Each onChange propagated to the parent caused a re-render with a fresh
Date instance, which retriggered the value-sync effect and overwrote
the in-progress segment state. Result: after picking a day in the
calendar, typing '14' in the hour field only kept the last digit.
Initial value still seeds segment state via the useState lazy
initializer, and Radix Popover unmounts the content on close so each
open starts from the current value.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Enter inside any segment now commits even when the value is partial.
Missing segments default to today (day, month, year) or to 00
(hour, minute). Out-of-range values clamp to their segment max,
and an impossible day-in-month (e.g. Feb 31) clamps to the last
day of the resolved month.
Calendar click no longer fires onCommit, so the Due popover stays
open and the date segments update inline. The popover closes via
Enter inside a segment, Esc, or click outside.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- New DateTimeField component: segment editor (DD/MM/YYYY HH:MM) honoring
FormatPrefs.dateFormat. Each segment is keyboard-driven (digits to
enter, arrows up/down to inc/dec, arrows left/right or separators to
move, Enter commits and closes the Due popover). Calendar grid below
stays in sync.
- TaskFormDialog: header gains px-5 pt-5 pb-2 so the title+description
don't sit flush against the DialogContent edges.
- AssigneesList: ArrowDown on the last list item now focuses the
"new name" Input; ArrowUp from the Input returns to the last list
item; Esc on the Input closes the popover.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add dueOpen state + onOpenChange so Esc and outside-click close the
popover consistently with the other pills; wire DateField.onCommit
to close the popover after Enter or calendar click.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two new keys for the DialogDescription line under the TaskFormDialog
header. Added to all five supported languages (en, it, es, fr, de).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switch the trigger element from a presentational <span> to a real
<button type="button"> with forwardRef so the pill can receive keyboard
focus, be used directly as a PopoverTrigger asChild, and show a
focus-visible ring. Public API stays compatible: icon, label, value,
empty plus any standard button HTML attributes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
withTime renders hour/minute selects under the Calendar and appends
HH:MM to the display text when the value has a non-midnight time.
flat renders Input + Calendar (+ Time row) inline without the
internal Popover, so a caller can embed DateField inside its own
popover without nesting. Existing callers continue to work
unchanged (both props default to false).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Existing callers unaffected: a bare date still parses to midnight as
before. New behavior: an optional trailing ' HH:MM' is stripped from
the input, the date portion goes through the existing resolution
order, and setHours/setMinutes is applied to the result. Out-of-range
times (e.g. 25:00, 12:99) return null.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Surface invalidMessage on end DateField when end < start
- Auto-clear endDate when start changes past it
- Add title tooltip on SelectTrigger when project is locked
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add useId() to generate stable fieldId/errorId; link <Input> to error
<p> via aria-describedby so screen readers announce the message.
- Add onMouseDown={e => e.preventDefault()} to the calendar icon Button
so clicking it does not trigger onBlur on the input mid-typing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>