316 Commits

Author SHA1 Message Date
Claude
a72adac613
Merge d2/components: ShellPage JSX components
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 10:26:15 +00:00
Claude
ebb3cecd97
refactor(web): share one PointerMenu for the space and section menus
The space and section context menus repeated the same dropdown with an
invisible trigger anchored at the pointer; only their label and items differ.

Move-only: PointerMenu in pages/shell renders that dropdown and both menus
pass their label, close handler and items. DOM, attributes and handlers are
unchanged, and the labels stay in ShellPage, so the catalogs do not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 10:08:48 +00:00
Claude
0ab148ba03
refactor(web): extract ShellPage side panel and computer JSX into components
The side panel header, the computer preview and the full-window computer
overlay were large inline blocks in ShellPage's render.

Move-only: they are now SidePanelHeader, ComputerPanelPreview and
FullscreenComputerOverlay in pages/shell, each with explicit props. The
booting screen, the routine list and the create-bot modal stay where they
are. DOM, classNames, test ids, aria attributes, handlers and messages are
unchanged; the catalogs only update their file references.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 10:08:48 +00:00
Claude
3c678519a6
refactor(web): extract ShellPage sidebar JSX into components
ShellPage's JSX carried the whole sidebar inline, including two near-copies
of the group header and of the main header's sidebar toggles, which made the
render hard to follow and easy to change in one place only.

Move-only: the sidebar header, group header (one component for the
conversation and project variants), the team and project roster rows, the
archived section, the user menu and the sidebar toggle buttons now live in
pages/shell. DOM, classNames, test ids, aria attributes, handlers and messages
are unchanged; the catalogs only update their file references, and the
desktop source checks for the header toggles now read the file they moved to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 10:08:43 +00:00
Claude
a0060b8df7
refactor(web): move Shell.tsx module-level code into pages/shell modules
Shell.tsx had grown to about 8,000 lines, roughly 2,200 of them module-level
components and helpers below ShellPage. That made the page hard to navigate
and review and turned every chat change into an edit of one hot file.

This is a move-only refactor: code is moved verbatim and only imports and
exports change. No renames, no JSX or memo boundary changes.

- shell/transcript.tsx: Transcript, QuoteSelectionButton, MessageHoverActions,
  MessageView
- shell/composer.tsx: Composer, slashActionLabel, MentionOptionIcon,
  MentionChipIcon, ATTACHMENT_ACCEPT
- shell/computer-screen.tsx: ComputerReleaseActions, embeddableScreenUrl,
  screenIframeSandbox, DesktopKindEmptyState, computerPlaceholder,
  computerLabel (all used by the computer pane)
- shell/thread-state.ts: threadSnapshotSignal, firstThreadRoute,
  applyThreadEvent, newClientNonce
- shell/roster-format.ts: stripSpaceTitlePrefix, formatRosterTime,
  cleanRosterPreview
- shell/message-preview.ts: previewMessageText (shared by composer and
  transcript)
- shell/collapsed-sidebar-sections.ts: collapsed sidebar storage helpers
- shell/shell-types.ts: PendingAttachment and FALLBACK_BOT_COLOR, shared by
  Shell.tsx, composer and transcript

Types, constants and one-off helpers used only by ShellPage stay in
Shell.tsx. The Lingui catalogs only change their file-reference comments;
message ids and translations are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 09:38:16 +00:00
Claude
3f22ef691f
Merge remote-tracking branch 'origin/main' into claude/phase3-media 2026-10-04 09:27:53 +00:00
cynarAI
6135bd8d63
feat: the main agent as a coach — conversational onboarding, goals, check-ins and push (#118)
* chore: remove legacy orchestrator, trading and one-off script leftovers

The repository still carried the pre-evOo prototype: a Python orchestrator,
the old HTML "evoo-platform" cockpit (whose test ran in the root unit
suite), an apex-trading package, the hot-reload deploy.sh for it, a
permissions manifest, an architecture note with local file links, agent
handoff notes, a Steve Jobs persona template and about ninety one-off
scripts that probe a developer database with hard-coded local URLs. None
of it is used by the product, CI or the real deploy in ops/evo-deploy.

Delete those files, drop the evoo-platform test glob from the root vitest
config, the Cockpit section from .env.example and the dead biome and
gitignore entries, and update the docs that listed the removed tools.
scripts/start-tmux.sh no longer starts the removed sentinel and resolves
the checkout from its own location instead of a machine-specific path;
scripts/seed-ollama.ts requires DATABASE_URL instead of falling back to a
local URL with a username.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* fix(web): drop trading and persona prompts from the conversation shell

The web shell still showed leftovers from the trading prototype: composer
chips with German trading and "Steve Jobs" prompts, trading suggestion
chips under the empty conversation line, a German trading hero for the
empty transcript, and a German "data protection" status panel in the
sidebar. They suggested actions the product does not have and added
persistent chrome.

Remove the composer chips and their helper, the empty-state chips and the
hero (the empty conversation keeps its existing one-line start hint), the
sidebar trading indicator and the data trust panel. The new-bot E2E now
asserts the plain empty state; the sidebar spec no longer expects the
trading status, and the placeholder Steve Jobs spec with no assertions is
gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* refactor: remove the trading bot status integration

With the sidebar trading indicator gone, the only remaining consumer of
the trading bot status was a banner in General settings that reused the
same component. evOo is a personal assistant; a status proxy and poller
for one external trading service, configured through service-specific
TRADING_BOT_URL and VITE_TRADING_BOT_URL variables, does not belong in
the core product.

Remove the settings banner and its prop plumbing, the web poller, the
indicator component, the /api/trading-bot/status proxy, the shared core
contract, the env loading and both variables from .env.example, together
with their tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* chore: remove one-off database scripts and a stale review note

packages/db carried four hand-run scripts outside the package build that
query or seed a developer database with a hard-coded connection string,
one of them for a specific account. docs/VOICE-FIRST-REVIEW.md was a
one-time verification note for a workflow that no longer exists and
named private machines and local paths. Nothing references them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(memory): remember appends dated notes instead of replacing memory

The remember tool rewrote the bot's whole MEMORY.md with each call, so every
new fact erased the earlier ones unless the model re-sent the full document.

- MemoryCommitRequest gains mode "append" (the store joins the note onto the
  current content inside the same serializable transaction and still writes a
  revision row) and every commit is capped at 16 KB per document.
- remember appends a dated bullet ("- YYYY-MM-DD: ...", the user's local date)
  by default and rewrites only with mode "replace"; a full document returns an
  error that tells the bot to condense it. Retries of the same tool call stay
  single appends through the existing effect record.
- Scripted runtime memory entries can append too; the scripted "remember"
  script now appends instead of overwriting the bot's memory.
- When memory does not fit the run's 32 KB budget, the prompt keeps a
  document's newest lines instead of its oldest.
- memory.update maps an oversized save to BAD_REQUEST.
- docs/PRODUCT.md records that UserFact is the cross-space user model and the
  space MEMORY.md stays space notes (no migration).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(coach): goals, habits, check-ins, daily briefs and check-in rhythm

The main agent should coach the user, but nothing stored what the user
wants to achieve or keep doing, and no routine ever checked in. This adds
the data model and the interfaces the onboarding intake and the coach
tools build on.

- Schema: UserGoal, Habit, CheckIn (one per habit, goal or day and date)
  and DailyBrief, all keyed to the user's local calendar date, plus a
  coachKey on Routine that marks the main bot's coach check-ins.
- Contracts (coach.ts): CoachRhythm, Goal, Habit, CheckIn, DailyBrief and
  CoachToday DTOs and the RPC inputs.
- Repository (@rakazo/db coach.ts): goal, habit and check-in CRUD with
  per-user limits, upsertDailyBrief and loadCoachToday with streaks in
  the user's time zone.
- coach-rhythm.ts: setCoachRhythm/getCoachRhythm keep up to three main
  bot routines (morning brief, evening check-in, weekly review) in the
  main conversation in step with the user's chosen times.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(notifications): add push subscriptions, notification settings and web push interfaces

Push could only reach one mobile device per user (a token file), quiet hours did not exist,
and the web app had no service worker, so a closed tab never heard about finished work.

This lands the shared interfaces first so onboarding can build on them:
- push_subscriptions (web push endpoints and Expo tokens, one row per device) and
  user_notification_settings (quiet hours as minutes in the profile time zone, nudges, a
  per-day nudge counter), with CHECK constraints.
- NotificationSettingsSchema and web push contracts, notifications.settings.get/update and
  notifications.webPush.config/subscribe/unsubscribe (owner-scoped, https endpoints only).
- getNotificationSettings/updateNotificationSettings plus the policy and nudge-cap helpers.
- VAPID keys from VAPID_PUBLIC_KEY/VAPID_PRIVATE_KEY or generated once and stored encrypted,
  and an SSRF-guarded web push sender built on the web-push package.
- The web app links its manifest, registers a push-only service worker where supported (secure
  context, not the desktop app) and exposes webPushSupported/webPushEnabled/enableWebPush/
  disableWebPush.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* chore: replace machine-specific paths and addresses with placeholders

ORIGIN.md pinned the intended checkout to an absolute path under a
personal home directory, docs/LOCAL-SETUP.md listed folders on one
developer's machine, and two tests used what looks like a real tailnet
address. Use a home-relative path, drop the machine-specific folder list,
and use 100.100.100.100 in the tests; it is still inside 100.64.0.0/10,
so both tests check the same thing as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test(coach): repository, streak and rhythm suites

Covers what the coach interfaces promise before anything builds on them:
goal and habit ownership and limits, one check-in per habit, goal and day
dated in the user's time zone, streaks across weekends and weeks, and a
rhythm that keeps exactly one main-bot routine per check-in and is
idempotent when set again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* refactor(contracts): share one clock-time schema

The coach rhythm and the quiet hours both parse "HH:MM" wall-clock times
and each defined its own ClockTimeSchema, which the contracts index cannot
export twice. Both now import it from one module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(onboarding): ask the onboarding questions in the main conversation

The wizard asked for the account name again and a "Tell us about yourself" form, and the
focus answer in the main conversation was never saved. The main bot now gets to know the
user in the main conversation instead, one card after each answer.

- UserOnboarding keeps the pending step, the focus and when onboarding finished.
- onboarding-intake.ts declares the steps (focus, goal, rhythm, quiet hours, notifications)
  as cards plus what their answers change; onboarding.ts commits an answer, the focus app
  suggestions and the next card or the closing line in one transaction.
- The choice block gains a step, an optional text input and the typed answer; choose takes
  the step, the text and whether the device can still turn push on.
- The focus answer becomes a goals fact; the goal, rhythm and quiet-hours effects and the
  push seam are placeholders until the coach and notification modules are merged.
- The wizard drops the preferences step and fills time zone, locale and language from the
  browser; the main bot's prompt notes open onboarding cards; agent history shows a card's
  question and answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(notifications): fan push out to every device, honour quiet hours, add notify_user

Run notifications reached at most one phone and nothing could hold them back at night.

- PushNotificationProvider sends each notification to all of the user's registered devices
  (web push and Expo), skips everything during quiet hours in the profile time zone, drops
  devices the push service reports gone (404/410, DeviceNotRegistered), records lastUsedAt and
  links each push to its conversation.
- Expo tokens move from DATA_DIR/push-tokens files into push_subscriptions: the API imports and
  deletes the legacy files at start; notifications.registerPush/unregisterPush keep their
  contract for the mobile app (tokens must look like Expo tokens).
- NotificationMessage gains the nudge kind, and the main bot gets notify_user: a short push to
  the main conversation that respects the nudge switch, quiet hours and a cap of three per day.
  It is withheld from shared messaging channels and from runs answering another owner's agent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(coach): main-bot coach tools and goals in its prompt

The main agent had no way to record what the user wants to achieve or how
their day went, and its prompt never showed their goals or habits.

- update_goal, update_habit, log_check_in and write_daily_brief, main-bot
  only and, like update_user_profile, withheld from shared channels and
  from runs that answer another owner's agent. Goals and habits can be
  named by id or exact title.
- The main bot's own runs list active goals and habits (with ids, done
  state and streak) in the <user> block; the basics-only block and every
  other bot never see them. The main agent role adds a short coaching
  stance and names each coach tool only when the run has it, so the
  check-in routine prompts stay tool-agnostic.
- The scripted runtime understands a few coach commands and the morning
  check-in, so tests can drive the tools end to end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* fix(web): use English Lingui sources for German UI text

Several web surfaces hard-coded German (the project card, roster and
activity status labels, the sidebar "Heute" label) or used German text as
the Lingui source message (sidebar sections, project timeline, bot picker,
voice entry, group stage, copy and read-aloud buttons). English users saw
German, and because production builds keep only message ids, none of these
strings could be translated per locale.

Convert them to English source messages, keeping the meaning:
- ProjectCreatedCard: Lingui Trans/Plural instead of literals.
- agent-activity: labels go through i18n._ with literal ids so the module
  stays importable from plain unit tests; the extractor still collects them.
- Shell, ProjectSessionTimeline, bot-picker, StageVoiceEntry, OoStage:
  English sources, reusing existing messages where one already fit
  ("just now", "{n}m ago", "Search", "No bots match", "Set up voice",
  "Group conversation", "Transcript").
- The conversation picker matches its create entry against the translated
  label instead of a fixed German string.

The German wording returns through the de catalog in the next commit.
Unit and E2E tests now assert the English labels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(web): keep Lingui catalogs complete and in sync

The committed catalogs were last extracted weeks ago: about 330 source
messages were missing from every locale, and production builds keep only
message ids, so those strings rendered as hash ids (sidebar sections,
privacy settings, profile, timeline, ...). German also had 226 empty
entries, and every extraction rewrote the files because references carried
line numbers.

- lingui.config.ts: po references without line numbers, and a stable sort
  that breaks Lingui's ordering ties (explicit ids, equal text) by id, so
  the output no longer depends on file-system order.
- Re-extract all catalogs with --clean (drops 25-44 obsolete entries per
  locale; no existing translation changed).
- Translate every German entry (du form, consistent with the existing
  catalog). Other locales keep empty entries and fall back to English.
- i18n-catalog-sync.test.ts extracts into a copy of the catalogs and fails
  when the committed files differ or a German entry is empty, so stale
  catalogs fail the unit test job (offline, about 5 s).

Adds @lingui/format-po (already installed through @lingui/cli) as a direct
dev dependency to configure the formatter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* refactor(memory): derive the run's memory budget from the document limit

The prompt budget was a separate 32 KB constant; it is now twice the
per-document limit, so a full bot document and a full space document keep
fitting if either number changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(profile): edit facts in place in About you

About you could only confirm or delete what the bots know about the user, so a
fact that was slightly wrong or filed under the wrong category had to be
deleted and taught again, and a fact could not be limited to some spaces from
the app even though the profile supports it.

- Each fact gets an Edit button that turns its row into an inline editor:
  wording, category, and (when the user has more than one space, or the fact
  is already limited) which spaces it shows in. Escape or Cancel leaves it
  unchanged; only changed fields are sent through profile.update editFacts.
- A limited fact names its spaces next to its source.
- The repository validates edits like new facts: a wording whose normalized
  form is empty or equals another fact of the user is refused, all edits of
  one update apply atomically, and the stored embedding is only dropped when
  the wording really changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(inbox): list and answer items from all of the user's spaces

The inbox in the main conversation only showed asks, approvals, takeovers and
unread threads of the current space. The main conversation lives in the home
space, so anything a bot needed in a project space never reached it.

- inbox.list now covers every space the user belongs to (except spaces being
  deleted), still only the user's own bots, runs and threads. Items from
  another space carry that space's id and name.
- The web inbox shows the space name next to the bot for those items, answers
  them with that space's header so the existing thread procedures check
  membership and ownership for that space, and opens their thread in it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(coach): coach RPCs for goals, habits, check-ins, today and rhythm

The web app needs to show and change the user's coach data, and the
check-in times need a way to change after onboarding.

- coach.today, coach.goals.*, coach.habits.*, coach.checkIns.* and
  coach.rhythm.get/set, owner-only and not space-scoped; another user's
  ids read as not found, invalid input as a bad request.
- coach-rhythm reuses the shared clock-time helpers.
- Tests: an API suite covering ownership and validation for every
  procedure, the coach tools driven through the main bot (and refused
  for a specialist), and a morning check-in routine that fires in the
  main conversation and writes today's brief; the unauthenticated
  authorization sweep lists the new procedures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(onboarding): save goal, check-in rhythm, quiet hours and push from the cards

With the coach and notification modules merged, the onboarding answers now take effect:

- the goal card creates a goal (source onboarding) through createGoal;
- the rhythm card sets the main bot's check-in routines through setCoachRhythm, and the
  closing line names the first check-in from the routines that were saved;
- the quiet-hours card updates the notification settings;
- the choice card tells the server whether this browser can still turn push on, so the
  notifications card is offered only there, and "Turn on" subscribes before answering.

The thread lock becomes NO KEY UPDATE so a rhythm routine, which references the thread, can
be written while the answer holds the lock. The goal answer is capped at the coach title
length. A Postgres suite walks the questions end to end: answers, skips, a dismissed card, a
stale and a doubled answer, another bot's focus card, and another user's access.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test(notifications): cover quiet hours, delivery, nudges and the notification RPCs

The quiet-hours CHECK accepted a row with only one bound set, because a NULL comparison made
the whole condition NULL; it now requires both bounds explicitly. The migration has not shipped
yet, so it is corrected in place.

- Unit: quiet-hours math across midnight, time zones and daylight saving; VAPID key handling
  (configured, generated once and shared, invalid); the web push sender (encrypted VAPID-signed
  request, gone on 404/410, no private or non-https endpoints); the fan-out with fake senders.
- PostgreSQL: settings and subscription repositories (constraints, per-user removal, endpoint
  hand-over, device cap, concurrent nudge cap, cascade on account deletion), delivery and
  notify_user against a real main conversation, and every notification RPC through the API
  including cross-user isolation and endpoint validation.
- .env.example documents the optional VAPID keys and VAPID_SUBJECT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(web): notification settings, and sign-out stops this browser's push

Users had no way to turn on push for a browser, set quiet hours or opt out of nudges.

Settings gains a compact Notifications section: a switch for push on this device (only where
web push works: a secure context outside the desktop app), quiet hours with from/to times
(22:00-07:00 when first turned on) and a switch for nudges. Changes save in order as they are
made. A browser that blocked notifications says so instead of offering a dead switch. Logging
out unregisters the browser first, so a shared computer stops receiving the account's pushes.

The E2E stubs the Push API (headless Chromium has no push service), walks all three controls,
checks they survive a reload and captures the notification-settings screenshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* fix(profile): keep the fact editor's Save label visible

Small buttons in ui-web merge their text-ui-label size over the default
variant's text color, so the small Save button rendered ink on ink. The
editor now uses default-size controls, with the category select at the same
height.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* refactor(notifications): keep the push sender contract next to the fan-out

The payload, target and outcome types are shared by the Expo and web push senders, so they now
live with PushSender in push-notifications.ts instead of in the web push adapter. The settings
E2E also stubs the Notification permission (headless Chromium reports it as denied even when
granted), and the quiet-hours inputs are wide enough for 12-hour times.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* fix(core): load child_process normally in the git checkout reader

readGitCheckout loaded node:child_process through require(), which does
not exist in the ESM API runtime. Linking a project space to a folder
that is a git checkout therefore failed after the space was created.
The lazy load is no longer needed: the reader is only reachable through
the server-only "@rakazo/core/git-checkout" subpath, never through the
package entry the web app bundles. Import spawn at the top instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(onboarding): ask before discovering projects and let the user choose

The onboarding scan started on its own, discarded what it found, claimed
to read email and calendars, simulated progress for everyone who could
not scan, and its project creation always failed (wrong Space fields and
a space lookup by title across tenants).

Discovery now asks first and creates nothing without a choice:

- The scan step appears only for the deployment owner when
  ONBOARDING_SCAN_HOME is set; everyone else skips it. The simulated
  scan is gone.
- It shows the folder and asks. Only directory listings are read: git
  projects up to four levels deep, their languages and a few tools
  (Docker, GitHub Actions, Terraform). Symlinks are never followed and
  each chosen folder is resolved again before use, so nothing outside
  the folder is reached. Results stay with the account that scanned.
- Found projects are a checklist (none preselected); found languages and
  tools are facts (preselected). "Add" creates one project space per
  chosen project with createSpaceForMember and wireProjectWorkspace in
  the user's own organization, reusing the user's own space already
  linked to that folder, and saves the chosen facts as onboarding facts
  under the profile lock and fact limit.
- When a connection becomes connected (direct connect, authorization
  round trip, or catalog reconciliation), "Uses <App>" is remembered as
  a tools fact from the connector. Revoking keeps it.

The E2E harness now points ONBOARDING_SCAN_HOME at a fixture folder and
starts without a deployment owner, so owner-only onboarding steps appear
only in specs that take the seat through /__e2e/deployment-owner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(web): coach on the Heute page

Heute only listed runs and linked to a hard-coded local Mission Control
address, so the coach's work had nowhere to show.

- Today's brief at the top, habits with a check-off and their streak,
  and active goals, each only when there is something to show; then the
  existing needs-you, doing and done lists.
- One icon button opens the check-in times (morning, evening, weekly),
  loaded when opened and saved on every change.
- The page reads coach.today, refreshes it when a run finishes or the
  tab becomes visible, and checks habits off optimistically; the date
  header uses the user's time zone from the profile.
- Removes the Mission Control link and its constant.
- Tests: helper unit tests and a web E2E that writes the brief through
  the main bot, checks a habit off across a reload and sets check-in
  times (screenshots heute-coach and heute-rhythm).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test(onboarding): walk the onboarding questions end to end; mobile reads cards as text

- onboarding-intake.spec walks focus, goal (after typing into the composer, which still
  works), check-ins, quiet hours and the notifications card when the browser offers it,
  with the onboarding-intake-goal, -rhythm and -done screenshots.
- completeOnboarding and the wizard specs drop the preferences step; the scan step is
  optional (skipped when it asks, otherwise it advances by itself).
- The user-profile spec now checks the browser-filled profile and the focus fact; the
  conversation spec checks the shorter app suggestion and the following goal question.
- The hover spec measures a reply that fills its row (a bot row's rail sits at the row's
  end, so the old long onboarding line was what made it pass) and scopes Send to the
  composer, since an open goal card has its own.
- Mobile shows a choice card as its question and answer instead of an empty bubble.
- The focus options drop their unused summary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test(web): stub the Notification permission without a static-only class

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test(web): find the sidebar Today button by its English label

The shell contrast test added on main looks up the sidebar button by the
old German source text, which this branch replaces with "Today".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(onboarding): show the onboarding cards and check-in names in the user's language

The onboarding cards, the closing line and the coach routine names were English server text
for every user. The server keeps the state and an English fallback; the web now words them
through Lingui, so there is one translation source.

- Choice cards: the web says the question, the option labels and the placeholder from the
  step and option ids, and keeps the server text for anything it does not know (quiet-hour
  ranges are already written in the user's locale). Another bot's focus card is recognized
  by its option ids. Mobile and agent history keep the server text.
- The "Got it. … are a good place to start." line is gone: the app cards speak for
  themselves, and with no cards nothing is said.
- The closing line becomes an intake_note block (note "done", nextCheckInAt, English text).
  The web words it with the first check-in in the user's clock; mobile blockText and
  blocksToAgentHistoryText read its text through their generic fallback.
- Routines carry coachKey. The routine list and editor show a check-in under its label in
  the UI language until the user renames it; saving an untouched label keeps the stored
  name. The English names move to contracts (COACH_ROUTINE_NAMES); prompts stay English.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* style: use top-level type imports in the coach changes

AGENTS.md asks for top-level `import type` instead of inline `type`
specifiers; this applies it to the import statements the coach work added
or changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* chore(web): extract catalogs and translate the coach strings to German

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test(web): look for the bot reply in the transcript only

The main conversation's sidebar row now previews the latest reply, so the
unscoped text lookup matched the sidebar too and failed in strict mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test(web): drop the trading status from the shell contrast test

main's contrast test now waits for the sidebar trading status before
measuring; this branch removes that status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test(onboarding): link the foreign folder while its tenant owns the deployment

Since #112 only the deployment owner can link a host folder, so the
discovery test creates the other tenant's linked space while that tenant
holds the deployment, then hands the deployment to the scanning owner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* chore(web): extract catalogs and translate the in-app browser strings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test: look up the home main conversation in the scheduling journey

Since every project has its own main conversation, the journey that
schedules for a specialist found the user's main conversation with
findFirst over all of them. With the Lab project's conversation also
matching, it picked either one, so the check failed about one run in
three. The lookup now names the home space, where the specialist joined
and the routine is created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test: expect the home main conversation's ask in another space's inbox

Every project now has a main conversation of its own, so an ask waiting in
the home main conversation is not visible from a project and its inbox
lists it, answered in the home space.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* fix(coach): move check-ins when the user's time zone changes

The coach check-ins are main-bot routines scheduled in the profile time zone, but a later
zone change (About you, onboarding, the main bot's update_user_profile tool, or forgetting
the whole profile) left them in the old zone until the rhythm was saved again, so they fired
at the wrong local time.

syncCoachTimeZone, next to the rhythm code, moves the main bot's coach routines that are not
in the current profile zone into it, recomputes the next run of armed ones and re-arms their
wakeup. Paused check-ins move but stay paused, routines the user made are never touched, users
without check-ins get none, and a save that leaves the zone as it is changes nothing. The
profile update and forget RPCs and the profile tool call it whenever they write the zone.

Quiet hours, the daily nudge cap and coach dates are evaluated in the profile zone when they
are used, so they need no migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* fix(web): word the main conversation preview in the user's language during onboarding

While onboarding wrote the main conversation, its sidebar row, project rows and Heute showed the
server preview: nothing for a pending card (a card has no text of its own, so the row fell back
to "Ready") and the English fallback of the closing note, even for a German user.

Groups now carry `previewBlock` next to `preview`: the latest message when it is an onboarding
card or note, validated with the same block schemas the messages use (now named exports). The
web words it with the helpers that render the card and the note, so no copy is duplicated: the
question while a card waits, the answer once given, and the closing note in the user's language
and clock. A dismissed card keeps the plain preview, as the conversation no longer shows it.
The field is left out for every other message, and mobile keeps using the plain `preview`.

The chosen-answer lookup moves from the card into onboarding-copy so the card and the preview
share it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test(web): look for the focus question in the conversation, not the sidebar

The main conversation's sidebar row now shows the pending intake question,
so a page-wide text match finds it twice (strict mode) or never reaches zero.
The specs mean the card in the open conversation, so they look there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-04 11:26:26 +02:00
Claude
08cff7b26a
Merge branch 'claude/phase2-coach' into claude/phase3-media
# Conflicts:
#	apps/mobile/lib/api.test.ts
#	apps/web/src/locales/de/messages.po
#	apps/web/src/locales/en/messages.po
#	apps/web/src/locales/es/messages.po
#	apps/web/src/locales/hi/messages.po
#	apps/web/src/locales/ko/messages.po
#	apps/web/src/locales/pt-BR/messages.po
#	apps/web/src/locales/ru/messages.po
#	apps/web/src/locales/tr/messages.po
#	apps/web/src/locales/zh-CN/messages.po
#	apps/web/src/pages/SettingsOverlay.tsx
#	apps/web/src/pages/shell/message-cards.tsx
#	packages/adapter-kit/src/index.ts
#	packages/adapters/src/builtin-tools.ts
#	packages/adapters/src/executor-tools.ts
#	packages/adapters/src/index.ts
2026-10-04 08:10:33 +00:00
Claude
86465067d6
Merge remote-tracking branch 'origin/main' into claude/phase2-coach
# Conflicts:
#	apps/web/e2e/heute-brief.spec.ts
#	apps/web/src/locales/de/messages.po
#	apps/web/src/locales/en/messages.po
#	apps/web/src/locales/es/messages.po
#	apps/web/src/locales/hi/messages.po
#	apps/web/src/locales/ko/messages.po
#	apps/web/src/locales/pt-BR/messages.po
#	apps/web/src/locales/ru/messages.po
#	apps/web/src/locales/tr/messages.po
#	apps/web/src/locales/zh-CN/messages.po
2026-10-04 08:05:32 +00:00
cynarAI
0c3e3f3d29
feat(web): complete German, English sources and catalogs that cannot go stale (#111)
* fix(web): use English Lingui sources for German UI text

Several web surfaces hard-coded German (the project card, roster and
activity status labels, the sidebar "Heute" label) or used German text as
the Lingui source message (sidebar sections, project timeline, bot picker,
voice entry, group stage, copy and read-aloud buttons). English users saw
German, and because production builds keep only message ids, none of these
strings could be translated per locale.

Convert them to English source messages, keeping the meaning:
- ProjectCreatedCard: Lingui Trans/Plural instead of literals.
- agent-activity: labels go through i18n._ with literal ids so the module
  stays importable from plain unit tests; the extractor still collects them.
- Shell, ProjectSessionTimeline, bot-picker, StageVoiceEntry, OoStage:
  English sources, reusing existing messages where one already fit
  ("just now", "{n}m ago", "Search", "No bots match", "Set up voice",
  "Group conversation", "Transcript").
- The conversation picker matches its create entry against the translated
  label instead of a fixed German string.

The German wording returns through the de catalog in the next commit.
Unit and E2E tests now assert the English labels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(web): keep Lingui catalogs complete and in sync

The committed catalogs were last extracted weeks ago: about 330 source
messages were missing from every locale, and production builds keep only
message ids, so those strings rendered as hash ids (sidebar sections,
privacy settings, profile, timeline, ...). German also had 226 empty
entries, and every extraction rewrote the files because references carried
line numbers.

- lingui.config.ts: po references without line numbers, and a stable sort
  that breaks Lingui's ordering ties (explicit ids, equal text) by id, so
  the output no longer depends on file-system order.
- Re-extract all catalogs with --clean (drops 25-44 obsolete entries per
  locale; no existing translation changed).
- Translate every German entry (du form, consistent with the existing
  catalog). Other locales keep empty entries and fall back to English.
- i18n-catalog-sync.test.ts extracts into a copy of the catalogs and fails
  when the committed files differ or a German entry is empty, so stale
  catalogs fail the unit test job (offline, about 5 s).

Adds @lingui/format-po (already installed through @lingui/cli) as a direct
dev dependency to configure the formatter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test(web): find the sidebar Today button by its English label

The shell contrast test added on main looks up the sidebar button by the
old German source text, which this branch replaces with "Today".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* chore(web): extract catalogs and translate the in-app browser strings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test: look up the home main conversation in the scheduling journey

Since every project has its own main conversation, the journey that
schedules for a specialist found the user's main conversation with
findFirst over all of them. With the Lab project's conversation also
matching, it picked either one, so the check failed about one run in
three. The lookup now names the home space, where the specialist joined
and the routine is created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-04 10:04:21 +02:00
cynarAI
ece3638ce7
fix(ui-web): teach cn() the design system's text and control scales (#115)
* fix(ui-web): teach cn() the design system's text and control scales

cn() used plain tailwind-merge, which only knows Tailwind's default scales.
It read the custom font sizes (text-ui-label, text-ui-caption, ...) as text
colors, so of a size and a color in one class list only the last survived:

- Small, extra-small and large buttons lost their variant's text color. A
  small primary button rendered foreground text on the primary fill (ink on
  ink in the light theme, near-white on white in the dark theme), and
  destructive, secondary and link buttons lost their colors the same way.
- Default-size and icon buttons, tab triggers and the typography helpers
  lost their font size and inherited the parent's instead.

The control and icon spacing scales (h-control-sm, size-icon-md, ...) were
unknown as well, so they never merged with other sizes: an explicit h-auto
or h-9 on a Button, Input or Toggle lost to the component's own height, and
large buttons and toggles drew their icons at the medium size.

cn() now extends tailwind-merge with the text and spacing keys styles.css
adds under @theme. A unit test probes cn() with every @theme key in
styles.css, so a new key that is not registered fails the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test: look up the home main conversation in the scheduling journey

Since every project has its own main conversation, the journey that
schedules for a specialist found the user's main conversation with
findFirst over all of them. With the Lab project's conversation also
matching, it picked either one, so the check failed about one run in
three. The lookup now names the home space, where the specialist joined
and the routine is created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-04 10:04:18 +02:00
cynarAI
59f4a1dfca
perf(api): filter peer-run activity in the transcript page query (#127)
* perf(api): filter peer-run activity in the transcript page query

The transcript loaded a raw page, dropped hidden peer-run rows in memory and
rescanned when nothing visible was left, so a long delegated stretch cost two
queries per hidden page. The page query now joins runs and excludes hidden
peer activity in Postgres, so every page is filled with visible rows. The
kept block kinds live in one list shared by paging, the in-memory filter and
live event forwarding.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTJRkJrXAS9e3aEFcFYTy8

* test: port the main-conversation journey fix from #126

Journey 5c fails on main since every space got its own main conversation:
the lookup picked the Lab conversation instead of the home one. Same change
as #126 (including its chart-links warm-up), so this PR can go green; it
no-ops once #126 lands.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTJRkJrXAS9e3aEFcFYTy8

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-04 07:41:48 +02:00
cynarAI
735322b016
ops: build the web app in the Evo deploy and add a preview-capable stack unit (#110)
* ops: build the web app in the Evo deploy and add a preview-capable stack unit

The Evo deploy restarted the dev stack without ever building the web app, so a
broken production build went unnoticed and the server could not serve the
build at all. Service workers and web push also need the production web app
behind HTTPS, which the deploy notes did not cover.

deploy.sh now builds the web app after install into a staging directory and
swaps it into apps/web/dist right before the restart, so a failing build
stops the deploy before migrations and the restart and never touches the
build a running vite preview serves. Any failure after the checkout rolls
back the code, reinstalls, regenerates the client, rebuilds the web app when
this deploy had already replaced it, restarts and health-checks; the outcome
is recorded as rolled-back or rollback-failed. The health check now fails
when the API check fails even if the web check passes. Build output goes to a
log that is printed only on failure.

start-stack.sh runs api, worker and sandbox supervisor as pnpm dev does and
serves the web app either from the Vite dev server (default) or from the
build with vite preview on the same port (EVO_WEB_MODE=preview). Preview mode
first checks that a build exists and that RAKAZO_HOST and SCREEN_PROXY_SECRET
resolve the way vite.config.ts resolves them, using the same secret guard.
evoo-local.service is a user unit template for it; the README documents
installing it, switching modes, the catalog caveat for production builds,
and HTTPS through Caddy with EXTRA_TRUSTED_ORIGINS.

A testkit test runs the real scripts against fixture checkouts with fake
pnpm, curl and restart hooks. It replaces the unwired shell regression test,
which exercised a copy of the SHA capture rather than deploy.sh itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* ops: show the build error instead of stack frames when a deploy build fails

A failing web build printed the last 40 lines of its log, which for a syntax
error are mostly bundler stack frames below the message that names the file
and line. Drop the frames before printing the tail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test: look up the home main conversation in the scheduling journey

Since every project has its own main conversation, the journey that
schedules for a specialist found the user's main conversation with
findFirst over all of them. With the Lab project's conversation also
matching, it picked either one, so the check failed about one run in
three. The lookup now names the home space, where the specialist joined
and the routine is created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-04 06:35:38 +02:00
Claude
9d6d85cb52
test(web): look for the focus question in the conversation, not the sidebar
The main conversation's sidebar row now shows the pending intake question,
so a page-wide text match finds it twice (strict mode) or never reaches zero.
The specs mean the card in the open conversation, so they look there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 04:34:57 +00:00
Claude
3e996b6c12
Merge f/media-stream: stream large media between the store and local computers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 04:20:07 +00:00
Claude
81991251a9
Merge f/comfy-progress: live ComfyUI progress over its websocket
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 04:20:07 +00:00
Claude
6af1be020e
feat(media): live ComfyUI progress over its websocket
A video generation runs a single ComfyUI prompt for minutes, and the media
card only advanced after each prompt finished, so it sat still until the
clip was done.

The ComfyUI provider now follows ComfyUI's standard /ws stream while its
prompts run and maps step progress to the job's overall fraction across
all prompts: never backwards, and short of each prompt's share until its
outputs are downloaded. The history poll stays the source of truth for
completion and errors; a socket that cannot connect, drops, or stays
silent leaves generation exactly as before, and it is closed on finish,
failure, cancel and abort. Each generation uses its own client id, so
concurrent jobs do not take over each other's messages.

The socket goes through a new createOpenAiCompatibleWebSocket next to the
guarded fetch: same URL and key checks, same resolved-address lookup,
ws/wss derived from http/https, the same Authorization header, a message
size cap, and a hard drop when a server never finishes the closing
handshake. It uses the undici package's WebSocket, so no dependency was
added. A provider given a replaced fetch without a matching socket opens
none, so tests and emulators never reach the network.

The ComfyUI emulator now serves the websocket and reports progress for
running prompts; provider, guard and conformance tests cover ordering,
multi-prompt mapping, unavailable and failing sockets, stray messages,
closing on cancel, and the address checks against a loopback server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 04:18:12 +00:00
Claude
31dcdf5293
Merge f/main-preview: localized main conversation preview
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 04:18:08 +00:00
Claude
a7ae619b28
Merge f/tz-rhythm: move check-ins when the time zone changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 04:18:08 +00:00
Claude
5362b4a796
fix(media): stream large media between the store and local computers
Attachments copied into a local computer's workspace were read whole from
the artifact store first, so a 500 MiB video sat in memory before it was
written to disk. attach_file did the same in the other direction: a large
file on a local computer was read whole before it was stored.

SandboxProvider gains optional readFileStream/writeFileStream. The desktop
(host workspace) provider implements them with the same path confinement
as its byte methods, one chunk in memory at a time, and removes a file
whose write fails or is aborted, including one that replaced an earlier
copy. The host-aware wrapper routes them, and providers without them
(remote computers, still capped at 12 MiB) receive the collected bytes.
Current-turn staging and attach_file now go through these helpers, and
unread streams are cancelled so the stored file is released. Media job
inputs stop copying when the job is cancelled.

The stream helpers move from the local artifact store into a shared
module so both use one implementation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 04:17:54 +00:00
Claude
a62e09f36b
fix(web): word the main conversation preview in the user's language during onboarding
While onboarding wrote the main conversation, its sidebar row, project rows and Heute showed the
server preview: nothing for a pending card (a card has no text of its own, so the row fell back
to "Ready") and the English fallback of the closing note, even for a German user.

Groups now carry `previewBlock` next to `preview`: the latest message when it is an onboarding
card or note, validated with the same block schemas the messages use (now named exports). The
web words it with the helpers that render the card and the note, so no copy is duplicated: the
question while a card waits, the answer once given, and the closing note in the user's language
and clock. A dismissed card keeps the plain preview, as the conversation no longer shows it.
The field is left out for every other message, and mobile keeps using the plain `preview`.

The chosen-answer lookup moves from the card into onboarding-copy so the card and the preview
share it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 04:17:32 +00:00
Claude
68f9dfb34b
fix(coach): move check-ins when the user's time zone changes
The coach check-ins are main-bot routines scheduled in the profile time zone, but a later
zone change (About you, onboarding, the main bot's update_user_profile tool, or forgetting
the whole profile) left them in the old zone until the rhythm was saved again, so they fired
at the wrong local time.

syncCoachTimeZone, next to the rhythm code, moves the main bot's coach routines that are not
in the current profile zone into it, recomputes the next run of armed ones and re-arms their
wakeup. Paused check-ins move but stay paused, routines the user made are never touched, users
without check-ins get none, and a save that leaves the zone as it is changes nothing. The
profile update and forget RPCs and the profile tool call it whenever they write the zone.

Quiet hours, the daily nudge cap and coach dates are evaluated in the profile zone when they
are used, so they need no migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 04:08:05 +00:00
Claude
aea604f7ca
Merge main (light palette) into claude/phase2-coach
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 03:54:55 +00:00
cynarAI
c4c5ce3c2d
fix(ui): polish the light palette, center the stage bloom (#108)
* fix(ui): make the light theme a real light theme

The web shell hardcoded dark surfaces (panel hex, white/black alphas,
white text), so switching to light left the sidebar, chat, composer and
side panel dark with ink text on top. Shell surfaces now use the shared
tokens: light gets a warm paper frame with white panels, hairline borders
and short soft shadows; dark keeps its look via dark: variants.

- Light palette: visible borders (border was equal to muted), white
  primary-foreground, lighter user bubble, readable warning/success.
- Selected sidebar row lives in one place (roster-row-style).
- Markdown, inline code, code blocks, quotes and scrollbars follow tokens.
- cn() now knows the text-ui-* scale is a font size; tailwind-merge
  treated it as a color and dropped labels' color on resized buttons
  (sign-up submit rendered ink on ink).
- Stage bloom was offset left by a double translateX.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016or6oqCqP8wbNafvhnbLQT

* refactor(ui-web): leave the cn() fix to #115

#115 fixes the same tailwind-merge bug more completely (text, control,
icon and spacing scales, guarded against styles.css) and adds the same
two files, so this branch drops its narrower copy to avoid the overlap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016or6oqCqP8wbNafvhnbLQT

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-04 04:37:24 +02:00
cynarAI
4ba7d843b1
test: fix journey 5c lookup and chart-links warmup (#126)
Look up the home main conversation by spaceId in journey 5c, and warm the chart-links E2E so the first Observable Plot import is not a 20s timeout.
2026-10-04 04:00:43 +02:00
Claude
182a9288c2
test: expect the home main conversation's ask in another space's inbox
Every project now has a main conversation of its own, so an ask waiting in
the home main conversation is not visible from a project and its inbox
lists it, answered in the home space.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 00:41:43 +00:00
Claude
55bfe9d291
test: look up the home main conversation in the scheduling journey
Since every project has its own main conversation, the journey that
schedules for a specialist found the user's main conversation with
findFirst over all of them. With the Lab project's conversation also
matching, it picked either one, so the check failed about one run in
three. The lookup now names the home space, where the specialist joined
and the routine is created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 23:46:36 +00:00
Claude
f071c2257c
test: look up the home main conversation in the scheduling journey
Since every project has its own main conversation, the journey that
schedules for a specialist found the user's main conversation with
findFirst over all of them. With the Lab project's conversation also
matching, it picked either one, so the check failed about one run in
three. The lookup now names the home space, where the specialist joined
and the routine is created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 23:46:36 +00:00
Claude
59811952ec
Merge the updated i18n branch (#111, with main) into the media integration branch
Catalogs are regenerated with the repo's extractor and keep both sides'
German translations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 23:26:12 +00:00
Claude
681dec2f3f
Merge main into the media integration branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 23:25:48 +00:00
Claude
52bbbbfb00
Merge the updated i18n branch (#111, with main) into the Phase 2 integration branch
Catalogs are regenerated with the repo's extractor and keep both sides'
German translations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 23:25:08 +00:00
Claude
77e822b9a7
Merge main into the Phase 2 integration branch
Main now gives every project its own main agent. Coaching stays with the
user's personal agent, the main bot of the home space: only it gets the
goal, habit, check-in, brief and nudge tools, the coach summary and the
open-onboarding note, and the tools refuse a project's main bot too. The
onboarding questions start with the personal agent only. A project the
scan creates gets its main agent like any other, and the inbox lists asks
from another space's main conversation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 23:24:21 +00:00
Claude
c7d543132d
Merge main into the i18n branch
The project and sidebar specs follow main's project main agents with the
English source labels; the project conversation label is translated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 23:16:21 +00:00
cynarAI
2c6b5c3d7b
chore: remove legacy orchestrator, trading and one-off leftovers (#109)
* chore: remove legacy orchestrator, trading and one-off script leftovers

The repository still carried the pre-evOo prototype: a Python orchestrator,
the old HTML "evoo-platform" cockpit (whose test ran in the root unit
suite), an apex-trading package, the hot-reload deploy.sh for it, a
permissions manifest, an architecture note with local file links, agent
handoff notes, a Steve Jobs persona template and about ninety one-off
scripts that probe a developer database with hard-coded local URLs. None
of it is used by the product, CI or the real deploy in ops/evo-deploy.

Delete those files, drop the evoo-platform test glob from the root vitest
config, the Cockpit section from .env.example and the dead biome and
gitignore entries, and update the docs that listed the removed tools.
scripts/start-tmux.sh no longer starts the removed sentinel and resolves
the checkout from its own location instead of a machine-specific path;
scripts/seed-ollama.ts requires DATABASE_URL instead of falling back to a
local URL with a username.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* fix(web): drop trading and persona prompts from the conversation shell

The web shell still showed leftovers from the trading prototype: composer
chips with German trading and "Steve Jobs" prompts, trading suggestion
chips under the empty conversation line, a German trading hero for the
empty transcript, and a German "data protection" status panel in the
sidebar. They suggested actions the product does not have and added
persistent chrome.

Remove the composer chips and their helper, the empty-state chips and the
hero (the empty conversation keeps its existing one-line start hint), the
sidebar trading indicator and the data trust panel. The new-bot E2E now
asserts the plain empty state; the sidebar spec no longer expects the
trading status, and the placeholder Steve Jobs spec with no assertions is
gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* refactor: remove the trading bot status integration

With the sidebar trading indicator gone, the only remaining consumer of
the trading bot status was a banner in General settings that reused the
same component. evOo is a personal assistant; a status proxy and poller
for one external trading service, configured through service-specific
TRADING_BOT_URL and VITE_TRADING_BOT_URL variables, does not belong in
the core product.

Remove the settings banner and its prop plumbing, the web poller, the
indicator component, the /api/trading-bot/status proxy, the shared core
contract, the env loading and both variables from .env.example, together
with their tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* chore: remove one-off database scripts and a stale review note

packages/db carried four hand-run scripts outside the package build that
query or seed a developer database with a hard-coded connection string,
one of them for a specific account. docs/VOICE-FIRST-REVIEW.md was a
one-time verification note for a workflow that no longer exists and
named private machines and local paths. Nothing references them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* chore: replace machine-specific paths and addresses with placeholders

ORIGIN.md pinned the intended checkout to an absolute path under a
personal home directory, docs/LOCAL-SETUP.md listed folders on one
developer's machine, and two tests used what looks like a real tailnet
address. Use a home-relative path, drop the machine-specific folder list,
and use 100.100.100.100 in the tests; it is still inside 100.64.0.0/10,
so both tests check the same thing as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test(web): drop the trading status from the shell contrast test

main's contrast test now waits for the sidebar trading status before
measuring; this branch removes that status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-10-04 01:13:52 +02:00
cynarAI
8449dec6c2
feat: give every project its own main agent (#125)
Each project (space) now has a main agent and a main conversation of its
own. The user talks to it, and it delegates to the project's specialists.
Creating a project sets both up and opens the conversation, and deleting
an otherwise empty project removes them with it.


Claude-Session: https://claude.ai/code/session_01R8hSsKTB3W3e61wD6kvzur

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-04 00:38:39 +02:00
Claude
0fd62b9823
fix(media): write poster frames as full-range JPEG
Newer ffmpeg releases refuse to encode JPEG from limited-range video
frames, so poster frames failed on CI's ffmpeg while older versions
accepted the same command. The poster is converted to full-range
yuvj420p before encoding, which every supported ffmpeg version takes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 21:57:20 +00:00
Cursor Agent
a51ba87133
fix(web): recover local settings boot when module fetch aborts
Lazy-load the desktop-settings page and its heavy sections so
loadWithRecovery can retry or reload after ERR_NETWORK_CHANGED.
Show the no-desktop alert immediately when the bridge is absent.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-10-03 21:08:49 +00:00
cynarAI
0d1939c775
ci: soft-fail mobile OTA when EXPO_TOKEN is missing (#124)
* ci: soft-fail mobile OTA when EXPO_TOKEN is missing

OTA publish is an infra helper; missing Expo credentials should not
fail the ci workflow or block evo-deploy after green Product CI.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* test(updater): narrow optional workflow jobs and format the OTA assertion

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015mFCmHk4zPyWpD9yY2C3yJ

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-03 23:04:17 +02:00
Claude
c782750245
Merge the updated i18n branch (#111, with main) into the media integration branch
Catalogs are regenerated with the repo's extractor and keep both sides'
German translations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 20:47:35 +00:00
Claude
93919ad3c2
Merge the updated legacy cleanup (#109, with main) into the media integration branch
The artifact pane from main loads and previews files through the streamed
artifact route like the file card, so its hook takes only the artifact id.
attach_file keeps media types and gains present, which marks image and
file blocks for the pane.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 20:46:57 +00:00
Claude
6e54c873c3
Merge the updated i18n branch (#111, with main) into the Phase 2 integration branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 20:40:37 +00:00
Claude
e1cd9be8a6
Merge the updated legacy cleanup (#109, with main) into the Phase 2 integration branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 20:40:03 +00:00
Claude
82fad0c9ac
Merge main into the i18n branch
Catalogs are extracted again; the artifact pane's row count is translated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 20:39:38 +00:00
Claude
3edce64bad
Merge main into the legacy cleanup
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUTKejm92a8Z7bgSunJChV
2026-10-03 20:34:35 +00:00
cynarAI
d912712668
ci: pass the attestation lookup's jq filter to gh api without -r (#120)
Every image publish on main failed at "Resolve registry attestation
eligibility" with "accepts 1 arg(s), received 2". The step ran
`gh api ... --jq -r '.owner.type'`, but gh api has no -r option: --jq
took "-r" as its filter and the real filter became a second positional
argument. gh api --jq already prints strings without quotes, so the
filter is now passed directly. The images were built and their
manifests pushed; only the attestation decision failed.

The workflow test now rejects `--jq -r`. Run with the repository's
values, the fixed step reports a personal account and a private
repository and skips attestation, as the guard intends.


Claude-Session: https://claude.ai/code/session_01EyQw3AEaHYXegvgvTPAsi1

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-03 22:25:48 +02:00
cynarAI
e28c3acf02
feat: let the pane beside a conversation follow the bot's work (#123)
The live screen opens wide beside the chat while the bot works on it or asks
for a takeover, a file the bot presents as its result opens next to the
conversation, and a question that scrolled away gets a "Needs you" pill back
to it. Shared core rules derive the focus from signals clients already have
(pointer events, takeover requests, a new `present` flag on attach_file,
pending asks), move the pane only on focus edges, never replace a pane the
user opened, and learn per bot whether the screen opens on its own. Pane
widths persist per device; mobile shows a pill that opens the computer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7VSRHHzyt2KwoCkUkZ2Gc
2026-10-03 22:23:46 +02:00
Claude
858bbc4e8e
Merge the updated i18n branch (#111, with main) into the media integration branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 18:52:19 +00:00
Claude
452106d7e5
Merge the updated legacy cleanup (#109, with main) into the media integration branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 18:52:19 +00:00
Claude
c7510fa9b3
Merge the updated i18n branch (#111, with main) into the Phase 2 integration branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 18:52:01 +00:00
Claude
adb4dff052
Merge the updated legacy cleanup (#109, with main) into the Phase 2 integration branch
The onboarding scan keeps the consent-first rewrite. Since main's folder
check moved out of wireProjectWorkspace, applying a scan now checks that
each chosen project is still a folder (not only inside the scan folder)
before anything is written; a discovery test covers a project replaced by
a file after the scan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 18:51:57 +00:00