Docker validate and Vite E2E on the same self-hosted host left signup
flakes; poll publish-server-image for this commit before Playwright starts.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Queued Playwright jobs were cancelled on self-hosted while waiting on
evoo-self-hosted-heavy. Keep validate matrix max-parallel: 1 for buildkit.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Self-hosted runs can compile Vite and warm the API slowly after image
validate; extend health, dev-server, signup, and job timeouts so Product
CI is not cancelled mid-suite.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
PR image validate and Playwright shared evoo-ci runners; overlapping
docker builds left E2E signup flakes and hit the 30-minute job cap.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Route git push and gh pr through the sandbox supervisor broker so project
computers never receive GITHUB_TOKEN/GH_TOKEN. Remove NOPASSWD sudo from the
computer image. Localize awareness strip copy via Lingui and wire
computer-image-tools.test.sh into the validate-computer-image job.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Add git, gh, openssh-client, curl, and sudo to the computer image with a docker
smoke test. Mount deployment-owner project workspacePath at /home/rakazo/project,
align /home/rakazo file paths with shell, and resolve default branch from origin/HEAD.
Inject the owner GitHub token from encrypted deployment settings only into project
computers; enforce branch-only push, block force-push, and require approval before
the first push or gh pr in a run. Add runaway guards (tool fuse and stall pause),
collapse live narration, fix streaming progress merge, DE run strings, and Lead as
the project main Oo name. Fix evo-deploy test flake.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* feat(web): routine list toggles and hide empty Routinen section
Show Routinen in the Oo side panel only when the bot has routines; remove
the panel create control. Each list row gets an active switch with optimistic
updates via routines.update. E2E covers visibility, toggle persistence, and
updates existing routine specs to create via RPC like chat.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): sync lingui catalogs after routine panel UI change
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): refresh routine list optimistically after save
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* test(e2e): wait for routine list after RPC create in golden path
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* style: format golden e2e after routine list wait
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): show sign-up while session lookup is pending
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Production readiness phase 1: journeys doc, onboarding persistence, teardown, chat UX
- Add docs/PRODUCTION-READINESS.md with journey matrix and defect backlog
- Persist onboarding draft in session storage across reload
- Localize onboarding discovery main-bot role; German Chief of Staff string
- Destroy team sandboxes and home folders when deleting an account
- Keep composer/transcript visible during queued runs; extend send watchdog
- Add Playwright specs: reload persistence, DE intake, queued progress, console guard
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Fix CI: sync Lingui catalogs, onboarding name draft, E2E stability
- Localize Chief of Staff in discovery shell; sync all locale PO files
- Propagate Oo name to onboarding draft while typing
- Adjust DE intake, queued-run, and console-error E2E specs
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Format chat-queued-progress E2E spec
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(api): scope account-delete sandbox teardown to owned spaces
Only destroy team sandboxes and homes for spaces the deleting user
owns with no remaining members or bots. Guard home removal under
dataDir/homes. Add unit and postgres journey coverage.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* chore: apply biome formatting
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Fix first-run onboarding UX: naming, locale, voices, Heute route, mobile drawer
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Fix biome import order and formatting
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Fix biome formatting on OoStage and E2E specs
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Fix E2E specs for onboarding name step and Heute reload route
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Fix German onboarding heading matcher in E2E
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Fix German signup E2E, Escape menu stacking, and Heute rhythm click
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Make onboarding E2E helpers locale-aware for German UI
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Fix completeOnboarding language skip for Skip · locale label
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Use language-step Skip · locale button in completeOnboarding
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Fix German E2E: Nachricht composer, voice loading wait, Heute rhythm
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Stabilize Heute reload URL and German voice E2E assertions
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Fix Heute bootstrap redirect and German onboarding voices
When Kokoro lists no locale-matched voices, onboarding now uses offline
presets instead of showing English-only options. Shell bootstrap skips
bot redirects while the user is on /app/heute.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Relax German voice E2E name matching for voice labels
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Harden Heute route and German voice onboarding E2E
Rebased onto main with Expo SDK 57 typecheck fixes. Voice options expose
stable test ids; Shell keeps /app/heute across bootstrap and redirects
legacy /app/heute bot URLs. Heute coach E2E asserts URL after reload.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Format Shell after Heute route helper
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Align Oo avatar eye spacing with evoo-logo overlap ratio
Derive small-eye placement from evoo-logo.svg center spacing (~0.94 of
r_big + r_small) for the shared rig, EvooMascot brand lock, and face crops.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Format brand-lock spacing test for Biome
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Align mobile Expo packages for install --check in CI
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* ci: retrigger after flaky chart-links E2E
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Increase header and sidebar icon scale on web and mobile
Raise shared --icon-sm/md/lg tokens (~25%) for web shell chrome, widen
chat header action controls, and align Expo home/thread header glyphs
with 44pt tap targets.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* ci: retrigger checks after flaky evo-deploy test
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Build jobs already push each architecture by digest. Tag those manifests
with ephemeral ci-${run_id}-${image}-${arch} refs instead of uploading
digest marker files to Actions artifact storage, which is blocked by the
account-wide quota.
Publish resolves handoff tags back into RUNNER_TEMP digest markers, then
assembles semver/edge tags and attestation as before. Handoff tags are
removed after publish with continue-on-error.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Pull updates from evOo instead of the upstream Rakazo repository
The in-app server updater, the image-based compose files and the desktop
auto-updater still pointed at elie222/rakazo, so updating an evOo install
would have pulled Rakazo. They now follow cynarAI/evOo-local, where the
publish workflow already pushes the images. The disabled Rakazo production
deploy job is gone from CI; evo deploys itself.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ
* Call the product evOo everywhere people see its name
The desktop setup window and menu, account emails, the iOS app name and
permission prompts, sign-in headings, messages bots post into connected chat
apps, the web fetch user agent, issue templates and the marketing site still
said Rakazo, or EvOo. They now say evOo. The marketing site no longer calls
evOo an alternative to itself.
Internal identifiers (package scopes, env vars, bundle ids, the desktop
productName that keys its data folder and keychain entry) stay as they are.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ
* Open conversation actions by holding a row on a phone
Pin, move to folder, mark unread, duplicate, archive and delete only opened
on right-click, which iOS Safari never fires. Holding a conversation or a
folder header now dispatches the same contextmenu event, so the existing menu
opens under the finger; Android's own long-press event is not doubled and the
lift does not also open the conversation.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ
* Give small chrome icons a 44px touch area on phones
The browser and computer toggles, the activity bell, the create button and the
section plus buttons were 18 to 36 px targets. On touch screens each now gets
an invisible 44px hit area without changing how it looks; the create button
uses the same plus icon as the rest of the sidebar instead of a text glyph,
and the computer toggle gets an accessible name.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ
* Add German to the mobile app
German speakers saw the phone app in English because the mobile UI only
knew English, Simplified Chinese, and Russian. The app now ships a German
catalog covering every mobile chrome string, follows a German device
language (de, de-DE, de-AT, de-CH, ...), and offers Deutsch in the Account
language picker.
Strings that also exist in the web German catalog reuse its wording so
web and phone say the same thing; the rest follow the same terms and the
informal "du".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ
* Use the evOo sign-in strings in the German phone catalog
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ
* Make account deletion work and give settings one section style
Deleting an account posted to the auth endpoint without a JSON content
type, so the server refused it and the button did nothing. The privacy
page now deletes through the auth client and returns to sign-in. It also
drops the claims it could not verify, the export button that only showed
an alert, and its own padding, and shows where the data and the computer
live. Notifications and media settings use the same SettingsSection.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ
* Use status tokens, the shared switch and one German glossary
Voice stage dots, the onboarding progress bar and the Voice First button
used raw emerald, violet, cyan and rose; they now use foreground, success
and destructive like the rest of the app. The routine editor's hand-built
switch is the shared Switch. German says Unterhaltung for threads and
Space consistently on web and phone.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ
* Expect the evOo setup copy in the desktop e2e specs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ
* Translate the voice preview label in the German phone catalog
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ
* Wait for the main thread URL without waiting for every resource
openMainBotThread already navigated with domcontentloaded; its URL check
then waited for the full load event, which can exceed 20 s on a busy
runner. The composer check that follows covers readiness.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Recommend Claude Opus 5.5 by bumping the Pi model catalog
The pinned pi-ai 0.85.1 had no Claude Opus 5.5, so the connect flow fell
back to Opus 5. pi-ai and pi-agent-core 0.87.1 add it, and the keyless
recommendation now preselects it.
The new catalog names router aliases "Qwen Max Latest (Qwen3.8 Max)";
those now read "(auto-updates)" like the other alias labels, so no
picker entry claims to be "latest".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD
* Name HTTPS as the cause when dictation is unavailable, and translate it
Over plain HTTP on a LAN address, Safari hides the microphone and speech
recognition, so voice mode said the browser had no dictation, in English
inside a German UI. Dictation now checks for a secure context first and
says to open the address with https://. Its other errors go through
Lingui too, and Safari's blocked recognizer gets a clear message instead
of "Dictation failed: not-allowed".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD
* Serve a fake Kokoro voice server in the E2E harness
Onboarding's voice step connects to the local Kokoro server on its
default port, so the shell E2E failed on any machine where nothing (or a
misbehaving server) listened there: the failed request shows up as a
browser console error. The harness now starts an offline stand-in and
points KOKORO_BASE_URL at it, so the suite no longer depends on the
runner host's own voice server.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD
* Show only the recommended sign-in until a model is connected
With no model connected, "Connect model" opened the full model settings:
provider list, tiers and a model picker, even though the recommendation
was already preselected. Until something is connected, the Models panel
now shows just the preselected model with its sign-in and API key field;
"Other providers" reveals the full settings.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD
* Give the second chart-links test the same cold-load allowance as the first
Each Playwright test runs in a fresh browser context, so the second test's
first navigation re-fetches the lazily imported Plot chunk just like the
first test's warm-up does. On a loaded CI runner that exceeded the default
20 s expect timeout.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD
* Use the recommended model as the Anthropic deployment default
A deployment that sets only ANTHROPIC_API_KEY and PI_DEFAULT_PROVIDER=anthropic
now runs Claude Opus 5.5, the same model the connect flow preselects, instead
of an older default.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD
* Run every space on the deployment's local models without a connect step
A deployment with PI_DEFAULT_PROVIDER=local and RAKAZO_LOCAL_MODELS set still
asked every user to connect a model, because only OpenRouter and Anthropic
counted as a deployment model. Configured local models now do, keylessly.
RAKAZO_LOCAL_TIERS assigns local models to the tiers the agent delegates to,
for spaces that have not mapped their own, and a RAKAZO_LOCAL_MODELS entry can
name its own server (id=url) when the models run behind different endpoints.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD
* Hide model choices when the deployment routes models itself
When the server provides its own models, the system picks and routes
them per task, so Settings no longer offers Models and bot settings no
longer offer Model and Thinking, on web and mobile.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD
* Wait for the workspace pane's width transition before measuring it
The pane animates its width over 300 ms, so a single boundingBox read
right after it opens can land mid-transition on a slow runner.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD
* Allow chart re-renders inside the chart-links loops to take longer
Each navigation in the loops renders Observable Plot again. On a loaded
CI runner that exceeded the default expect timeout even after the
warm-up, so the per-iteration visibility checks get 30 s.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD
---------
Co-authored-by: Claude <noreply@anthropic.com>
Move restart-mode prerequisites (systemd unit default, tmux, compose file,
script hook, EVO_SYSTEMD_SCOPE) ahead of git fetch/install/build/migrate.
Explicit systemd with no EVO_SYSTEMD_UNIT falls back to EVO_SYSTEMD_AUTO_UNIT.
die() after checkout triggers rollback; rollback marks in-progress to avoid loops.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* Export videos for named platforms and check each file against its limits
Exports only knew aspect ratios, so a cut could still be rejected or
degraded on upload: too long for the platform, over its size limit, or
far off its loudness. export_video now takes platforms (TikTok,
Instagram Reels and Feed, YouTube Shorts and YouTube, Facebook Reels,
LinkedIn, X, Pinterest, Snapchat Spotlight), up to six targets with the
presets. Each platform is rendered to its frame with the export's fit,
its frame rate cap, a bitrate cap that also keeps the file under the
size limit, and its loudness target in one loudnorm pass.
Every render is probed and measured afterwards. A file over the size
limit is encoded once more at a bitrate scaled from what it averaged,
and audio more than 2 LU off the target gets one linear loudness pass.
The result is stored on the output as a pass, warn or fail check with
language-neutral issue codes. A platform the video is too long or too
short for is not rendered: the card lists it with the reason, the
other targets still render, and the bot is told to trim first.
The limits come from the earlier profile research, with its sources,
review date and unverified flag; content, disclosure and caption rules
are left out because they need inference or a review flow. Preset
definitions move to their own module so the platform schemas can use
their aspect ratios without an import cycle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* Add remove_silence step to edit_video
Talking-head clips are mostly cut by hand to drop pauses, and the bot had
no way to find them: it could only keep segments it guessed. The new
remove_silence step finds every pause quieter than threshold_db (default
-35 dB) and at least min_silence long (default 0.6 s), keeps padding
(default 0.12 s) on each side so word onsets survive, and cuts the rest
through the existing keep-segments graph, so picture and sound stay in
sync.
Steps run in order, so silence is detected on the timeline the steps
before it leave: the planner gets an analysis mode that sends that audio
through silencedetect into a null output, and ametadata prints the marks
to a file in the job directory. The detected speech is handed back to the
planner per step. Nothing silent makes the step a no-op; nothing left
fails with a clear error. At most 200 pieces are kept, bridging the
shortest pauses first, so the filter graph stays a sensible size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* Offer platforms in the media card's export menu and show their checks
The card's Export menu now lists the platforms first, by brand name and
untranslated, then the aspect ratios after a separator; the platform an
output was made for is left out, like its own shape. A platform export
shows the platform's name under the player, or, when its check did not
pass, one line for the first issue instead ("Too long for TikTok (max
10:00)"), in the warning or destructive color. Skipped platforms get
the same kind of line, and they stand in for the error of a job that
skipped every platform, so the card does not say the same thing twice
in two languages.
The E2E spec seeds a platform export with a failed check and a skipped
platform, and captures the card and the open menu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* Add analyze_video to find cut points for the bot
To cut a social video well the bot has to know where the cuts fit, and it
could not look at a video at all. analyze_video is a read-only media job:
one ffmpeg pass prints scene changes (scene score above 0.3, one per cut),
pauses (silencedetect with the remove_silence defaults) and short-term
loudness (ebur128) to files in the job directory, and a transcript made
from the video by transcribe (an SRT whose parent is the video) adds when
each line is spoken. The job stores nothing; its card just says done.
The result reaches the bot through the existing media job path: the job
always wakes the bot that started it (the tool has no continue_after) and
the summary carries a short legend plus compact JSON. The runtime's
summary limit grows to 12,000 characters and the summary is fitted into
it: at most 200 entries per list, then every list shrinks evenly, keeping
the strongest cuts, the longest pauses and the start of the transcript,
with omitted counts. The wake prompt no longer claims the card shows the
result when a job made no files.
Jobs read derived artifacts through a new fetchDerivedArtifact, scoped to
the job's user like fetchArtifact. A migration admits the operation in the
media_jobs check and adds the tool to existing Studio specialists that
still edit video. The web card gets an icon; no new copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* fix(media): keep one skipped entry per platform on the card
A job that recovers after a crash runs again and reports the same
skipped platforms; the newest reason per platform now replaces the
earlier one instead of listing it twice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* chore(db): add the widened media job operation check as NOT VALID and validate it separately
Follows the repository's migration convention: the constraint commits
without scanning media_jobs, and a second migration validates it with a
lock that lets reads and writes continue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* Brand videos with a logo overlay and an intro before the cut
edit_video gains an overlay step that lays a PNG, JPEG or WebP image over the
video at a corner or the center, sized to a share of the frame width, with
optional opacity and time range; tall frames keep the 9:16 safe areas clear.
concat takes at: "start" so an intro can go before the video. The image is
authorized like other inputs, read with its own demuxer and refused above
8192 pixels on a side. New Studio specialists are told to brand with the
user's logo and intro.
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>
* Pick the voice from a carousel in the app language, local voice only
Voice settings now list only the voices that speak the app language
(German shows German voices, English shows English ones) as a horizontal
carousel: one card per voice, a play button next to the name, tap to
make it the active voice. Changing the app language moves the saved
voice to that language. The hosted voice providers (ElevenLabs, OpenAI,
Cartesia, Fish Audio) are removed; Kokoro on the server is the only
voice. E2E runs against a stub Kokoro server so the carousel is covered.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxoSHAyzMXvdtwcDJZcn57
* test(e2e): measure the workspace panel width after its transition settles
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxoSHAyzMXvdtwcDJZcn57
---------
Co-authored-by: Claude <noreply@anthropic.com>
The memory pane opened with an untranslated provider description, a
provider picker, a scope toggle and API key fields, and pushed what the
agents actually remember to the bottom. Most people never connect a
semantic-recall provider, so that setup now sits behind a collapsed
Advanced disclosure (open while a provider is connected), and the
remembered documents come first.
- Drop the provider descriptions, the "Shared documents" heading and
the per-document revision label.
- Replace the Isolated/Shared toggle pair with one "Share between bots"
switch.
- Remove the unused standalone dialog mode of the memory pane.
- German: call the section "Gedächtnis" so it no longer reads like
reminders ("Erinnerungen").
Claude-Session: https://claude.ai/code/session_01LWzKXyE6uDmmmzuod4oXNQ
Co-authored-by: Claude <noreply@anthropic.com>
The encounter view showed an uppercase "Transcript" / "Conversation record"
label and "Start the conversation — <bot> is on stage above." before any
message. Both repeat what the screen already shows, so they are removed.
Claude-Session: https://claude.ai/code/session_01Vyov34VJPqjo6YgiGHnuEL
Co-authored-by: Claude <noreply@anthropic.com>
* Export videos for named platforms and check each file against its limits
Exports only knew aspect ratios, so a cut could still be rejected or
degraded on upload: too long for the platform, over its size limit, or
far off its loudness. export_video now takes platforms (TikTok,
Instagram Reels and Feed, YouTube Shorts and YouTube, Facebook Reels,
LinkedIn, X, Pinterest, Snapchat Spotlight), up to six targets with the
presets. Each platform is rendered to its frame with the export's fit,
its frame rate cap, a bitrate cap that also keeps the file under the
size limit, and its loudness target in one loudnorm pass.
Every render is probed and measured afterwards. A file over the size
limit is encoded once more at a bitrate scaled from what it averaged,
and audio more than 2 LU off the target gets one linear loudness pass.
The result is stored on the output as a pass, warn or fail check with
language-neutral issue codes. A platform the video is too long or too
short for is not rendered: the card lists it with the reason, the
other targets still render, and the bot is told to trim first.
The limits come from the earlier profile research, with its sources,
review date and unverified flag; content, disclosure and caption rules
are left out because they need inference or a review flow. Preset
definitions move to their own module so the platform schemas can use
their aspect ratios without an import cycle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* Add remove_silence step to edit_video
Talking-head clips are mostly cut by hand to drop pauses, and the bot had
no way to find them: it could only keep segments it guessed. The new
remove_silence step finds every pause quieter than threshold_db (default
-35 dB) and at least min_silence long (default 0.6 s), keeps padding
(default 0.12 s) on each side so word onsets survive, and cuts the rest
through the existing keep-segments graph, so picture and sound stay in
sync.
Steps run in order, so silence is detected on the timeline the steps
before it leave: the planner gets an analysis mode that sends that audio
through silencedetect into a null output, and ametadata prints the marks
to a file in the job directory. The detected speech is handed back to the
planner per step. Nothing silent makes the step a no-op; nothing left
fails with a clear error. At most 200 pieces are kept, bridging the
shortest pauses first, so the filter graph stays a sensible size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* Offer platforms in the media card's export menu and show their checks
The card's Export menu now lists the platforms first, by brand name and
untranslated, then the aspect ratios after a separator; the platform an
output was made for is left out, like its own shape. A platform export
shows the platform's name under the player, or, when its check did not
pass, one line for the first issue instead ("Too long for TikTok (max
10:00)"), in the warning or destructive color. Skipped platforms get
the same kind of line, and they stand in for the error of a job that
skipped every platform, so the card does not say the same thing twice
in two languages.
The E2E spec seeds a platform export with a failed check and a skipped
platform, and captures the card and the open menu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* Add analyze_video to find cut points for the bot
To cut a social video well the bot has to know where the cuts fit, and it
could not look at a video at all. analyze_video is a read-only media job:
one ffmpeg pass prints scene changes (scene score above 0.3, one per cut),
pauses (silencedetect with the remove_silence defaults) and short-term
loudness (ebur128) to files in the job directory, and a transcript made
from the video by transcribe (an SRT whose parent is the video) adds when
each line is spoken. The job stores nothing; its card just says done.
The result reaches the bot through the existing media job path: the job
always wakes the bot that started it (the tool has no continue_after) and
the summary carries a short legend plus compact JSON. The runtime's
summary limit grows to 12,000 characters and the summary is fitted into
it: at most 200 entries per list, then every list shrinks evenly, keeping
the strongest cuts, the longest pauses and the start of the transcript,
with omitted counts. The wake prompt no longer claims the card shows the
result when a job made no files.
Jobs read derived artifacts through a new fetchDerivedArtifact, scoped to
the job's user like fetchArtifact. A migration admits the operation in the
media_jobs check and adds the tool to existing Studio specialists that
still edit video. The web card gets an icon; no new copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* fix(media): keep one skipped entry per platform on the card
A job that recovers after a crash runs again and reports the same
skipped platforms; the newest reason per platform now replaces the
earlier one instead of listing it twice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* chore(db): add the widened media job operation check as NOT VALID and validate it separately
Follows the repository's migration convention: the constraint commits
without scanning media_jobs, and a second migration validates it with a
lock that lets reads and writes continue.
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>
The profile had its own settings tab although it is a few rows of personal
details. It now sits as one card under Account in General, hides itself
while empty, and the separate tab is gone.
Claude-Session: https://claude.ai/code/session_017gHYZ4c9Twex73v4ZasmMJ
Co-authored-by: Claude <noreply@anthropic.com>
* Export videos for named platforms and check each file against its limits
Exports only knew aspect ratios, so a cut could still be rejected or
degraded on upload: too long for the platform, over its size limit, or
far off its loudness. export_video now takes platforms (TikTok,
Instagram Reels and Feed, YouTube Shorts and YouTube, Facebook Reels,
LinkedIn, X, Pinterest, Snapchat Spotlight), up to six targets with the
presets. Each platform is rendered to its frame with the export's fit,
its frame rate cap, a bitrate cap that also keeps the file under the
size limit, and its loudness target in one loudnorm pass.
Every render is probed and measured afterwards. A file over the size
limit is encoded once more at a bitrate scaled from what it averaged,
and audio more than 2 LU off the target gets one linear loudness pass.
The result is stored on the output as a pass, warn or fail check with
language-neutral issue codes. A platform the video is too long or too
short for is not rendered: the card lists it with the reason, the
other targets still render, and the bot is told to trim first.
The limits come from the earlier profile research, with its sources,
review date and unverified flag; content, disclosure and caption rules
are left out because they need inference or a review flow. Preset
definitions move to their own module so the platform schemas can use
their aspect ratios without an import cycle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* Offer platforms in the media card's export menu and show their checks
The card's Export menu now lists the platforms first, by brand name and
untranslated, then the aspect ratios after a separator; the platform an
output was made for is left out, like its own shape. A platform export
shows the platform's name under the player, or, when its check did not
pass, one line for the first issue instead ("Too long for TikTok (max
10:00)"), in the warning or destructive color. Skipped platforms get
the same kind of line, and they stand in for the error of a job that
skipped every platform, so the card does not say the same thing twice
in two languages.
The E2E spec seeds a platform export with a failed check and a skipped
platform, and captures the card and the open menu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* fix(media): keep one skipped entry per platform on the card
A job that recovers after a crash runs again and reports the same
skipped platforms; the newest reason per platform now replaces the
earlier one instead of listing it twice.
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>
Showing the right side panel (computer preview and routines) used to boot a
stopped computer right away. A boot is slow and heavy on the host, and most
of the time the panel is opened for routines, not the screen. The panel now
only reconnects a computer that already runs; a resting computer shows its
Open action without hover, and Open still boots it. A stopped computer is no
longer treated as needing maintenance.
Claude-Session: https://claude.ai/code/session_01TafdPTJdWxTYt9SYbDuKQ8
Co-authored-by: Claude <noreply@anthropic.com>
A manual deploy.sh run without a SHA resolved the local 'main' first,
which lags behind origin/main in the deploy checkout, and redeployed the
old commit. Resolve <remote>/<ref> first and fall back to the ref itself
for SHAs and tags.
Claude-Session: https://claude.ai/code/session_01YMUEBB4pr4TtGSMhEAKonM
Co-authored-by: Claude <noreply@anthropic.com>
The empty-chat stage offered "Start talking" / "Set up voice" above the
transcript, a second entry to the same voice conversation the composer's
mic now opens. Keep the composer as the single way in.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XcoERsCTbvjqDzE8qC82Wo
Text sizes now follow a --font-scale variable that is 1 on wider screens and
8/7 below 768px, so the 14px base text reads at 16px on phones (Safari stops
zooming into fields at that size) and every other size grows by the same
ratio. One-off pixel sizes in the shell move onto the shared text tokens so
they scale too. The stage subtitle now truncates inside the screen instead
of spilling past both edges.
Claude-Session: https://claude.ai/code/session_01PFEDgbyJm12ExL3Kn4Dk8B
Co-authored-by: Claude <noreply@anthropic.com>
An empty draft shows the mic where the send arrow sits; it opens the
voice conversation as the separate Voice button did. As soon as the draft
holds text, a chip or an attachment, the slot turns back into Send. This
frees the width the extra Voice pill took on small screens.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XcoERsCTbvjqDzE8qC82Wo
The deployment owner adds folders of photos and videos in the media settings. A per-folder
background job indexes them (relative path, kind, size, capture date, dimensions, duration),
re-indexes every six hours, and drops files that are gone. The owner's own runs get two tools:
search_media (words in folder and file names, kind, capture date range, newest first) and
import_media (copies one file into the conversation as an artifact). Files are never changed;
hidden entries and symlinks are skipped, and an import must resolve inside its folder.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy1md9QE1ayJbDCwJ4vAQs