diff --git a/.changelog/NEXT.md b/.changelog/NEXT.md index 18100a3dd..ad882c450 100644 --- a/.changelog/NEXT.md +++ b/.changelog/NEXT.md @@ -13,6 +13,7 @@ - **[wire-proactive-cos-speech-to-real-triggers] The assistant can now speak up on its own.** With proactive voice enabled, your Chief of Staff speaks first on three kinds of moments — a critical system error, a new task becoming ready, and a high-priority notification — instead of only talking back when you address it. Each kind is rate-limited on its own timer so a burst can't talk over you, and all of it still respects quiet hours and the proactive-voice switch. ## Changed +- **[migrate-two-remaining-local-formatters] Consolidated duplicate date/time and duration formatters.** The calendar event-detail panel and the Chief of Staff task list now use the shared formatting helpers instead of near-identical local copies — no change to how event dates/times or task duration estimates appear. - **[dedupe-hhmm-time-window-helpers] Shared time-of-day helpers.** Voice quiet-hours and time-windowed dashboard layouts now share one implementation for parsing HH:MM times and deciding whether the current time falls inside a window, so the two can't quietly drift apart. - **[extract-compare-helpers-once-eight-callers] Shared dashboard refresh-dedupe helpers.** The dashboard widgets that skip needless re-renders when their polled data hasn't visibly changed now share one comparison implementation, so the dedupe logic can't drift between widgets. - **Universe page route renamed `/universe-builder` → `/universes`.** `/universes` is now the list/table index; the editor lives at `/universes/:universeId`, and `/universes/new` is the create-mode entry point (the `UniverseBuilder` editor treats the `new` sentinel as no-id → blank draft; real ids are UUIDs so no collision). The "Universe" sidebar entry is relabeled "Universes" and points at the index; the editor header gains a "← All Universes" back link. Legacy `/universe-builder*` (and the older `/media/universe-builder*`) paths redirect to `/universes*` preserving search + `#canon`, so existing bookmarks and in-app deep-links keep working. The `/api/universe-builder/*` backend endpoints are unchanged. `useUniverseNav`'s exported `universeBuilderBasePath` is renamed `universesBasePath`; the nav-manifest entry + voice/`⌘K` aliases follow the new path. diff --git a/PLAN.md b/PLAN.md index 9722a8644..f267f36ab 100644 --- a/PLAN.md +++ b/PLAN.md @@ -5,7 +5,7 @@ For project goals, see [GOALS.md](./GOALS.md). For completed work, see [.changel ## Next Up - [ ] [codex5-onboarding-capability-map] **[P2][ONBOARDING]** Capability map of connected systems. One page showing each integration's status: Providers (per-provider configured/available/throttled), Calendar, Brain/memory embeddings, Voice (mic + TTS), Tailscale + HTTPS, Genome/health imports, Telegram/messages, App registry/PM2. Each row links to the relevant settings page. Doubles as a setup checklist and a runtime health overview. -- [ ] [migrate-two-remaining-local-formatters] **Migrate two remaining local formatters surfaced during the module-discovery PR.** Two additional local format definitions were found that aren't in the original tracker: (1) `client/src/components/calendar/EventDetail.jsx:10` defines `formatDateTime(dateStr, isAllDay)` with an all-day branch the shared `formatDateTime` lacks — extend the shared helper to take `{ allDay: true }` or keep this local and rename to `formatEventDateTime`. (2) `client/src/components/cos/tabs/TaskItem.jsx:78` defines `formatDurationMin(mins)` with a `~` approximation prefix (`~3h 30m`) — extend shared `formatDurationMin` with an `{ approximate: true }` option. Low priority; visual semantics need to be preserved exactly. +- [ ] [migrate-taskitem-formatattachmentsize-to-formatbytes] **Migrate `TaskItem.jsx`'s local `formatAttachmentSize` to shared `formatBytes`.** `client/src/components/cos/tabs/TaskItem.jsx` still defines a local `formatAttachmentSize(bytes)` (B/KB/MB with `.toFixed(1)`) that duplicates `formatBytes` in `client/src/utils/formatters.js`. Surfaced by gemini review during the `[migrate-two-remaining-local-formatters]` claim 2026-05-24; deferred as out-of-scope for that item (which named only `formatDateTime` + `formatDurationMin`). Note the edge-case semantics differ: the local helper returns `''` for falsy/0 size (used inside an attachment `title` like `(1.5 KB)`), while `formatBytes(0)` returns `'0 B'` — preserve the empty-on-missing behavior with a guard (`att.size ? formatBytes(att.size) : ''`) so the title doesn't render `(0 B)` for sizeless attachments. - [ ] [optimize-voice-ui-index-text-payload-lazy-only-run] **Optimize `voice:ui:index` text payload.** Lazy: only run `extractVisibleText` when server requests via `voice:ui:read-request`. Keep current behavior as fallback. - [ ] [voice-agent-explicit-long-term-memory-routing-on] **Voice agent — explicit long-term memory routing.** On retrieval-shaped voice turns, inject top-N relevant memories into the system prompt via `brain_search`. - [ ] [flux2-multi-reference-python-runner] **FLUX.2 multi-reference Python runner.** The UI + server contract for multi-reference editing shipped 2026-05-17 (slug `multi-reference-image-editing-for-flux-2-ui`); the Python runner (`scripts/flux2_macos.py`) currently ignores the `--reference-images`/`--reference-strengths` args that `local.js` now passes. Wire diffusers' multi-reference API in the runner and swap `server/lib/mediaModels.js#flux2-klein-9b` `tokenizerRepo` to `FLUX.2-klein-9B-kv` (gated repo — requires the user to accept the license on HF). Validate end-to-end with 2–4 uploaded refs. diff --git a/client/src/components/calendar/EventDetail.jsx b/client/src/components/calendar/EventDetail.jsx index aab1ae11d..ca64b8539 100644 --- a/client/src/components/calendar/EventDetail.jsx +++ b/client/src/components/calendar/EventDetail.jsx @@ -1,4 +1,5 @@ import { X, MapPin, Clock, Users, Repeat, CalendarDays } from 'lucide-react'; +import { formatEventDateTime } from '../../utils/formatters'; const RSVP_STYLES = { accepted: 'bg-port-success/20 text-port-success', @@ -7,11 +8,6 @@ const RSVP_STYLES = { none: 'bg-gray-700 text-gray-400' }; -function formatDateTime(dateStr, isAllDay) { - if (isAllDay) return new Date(dateStr).toLocaleDateString([], { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }); - return new Date(dateStr).toLocaleString([], { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); -} - export default function EventDetail({ event, onClose }) { return (
@@ -47,13 +43,13 @@ export default function EventDetail({ event, onClose }) {
) : ( <> -
{formatDateTime(event.startTime, false)}
+
{formatEventDateTime(event.startTime)}
to
-
{formatDateTime(event.endTime, false)}
+
{formatEventDateTime(event.endTime)}
)} {event.isAllDay && ( -
{formatDateTime(event.startTime, true)}
+
{formatEventDateTime(event.startTime, { allDay: true })}
)} diff --git a/client/src/components/cos/tabs/TaskItem.jsx b/client/src/components/cos/tabs/TaskItem.jsx index c65fea962..fc5904fdd 100644 --- a/client/src/components/cos/tabs/TaskItem.jsx +++ b/client/src/components/cos/tabs/TaskItem.jsx @@ -20,6 +20,7 @@ import { import toast from '../../ui/Toast'; import * as api from '../../../services/api'; import { filterSelectableModels } from '../../../utils/providers'; +import { formatDurationMin } from '../../../utils/formatters'; const statusIcons = { pending: