Commit Graph
2129 Commits
Author SHA1 Message Date
Bohdan Triapitsyn 92abdefea8 fix: stage rebuilt better-sqlite3 for Electron packaging
Rebuilds better-sqlite3 separately for Electron before packaging
Copies the native binary into the packaged app resources
Updates packaging docs to explain the afterPack workaround
2026-07-28 14:05:03 +03:00
Bohdan Triapitsyn fbc064c16f docs: add agent control tool guide
Adds a new docs page explaining the OpenChamber agent control tool and its capabilities.
Includes localized versions for supported languages and adds the page to the docs sidebar.
Updates the lockfile with the new better-sqlite3 dependency.
2026-07-28 12:58:20 +03:00
Bohdan Triapitsyn 8d9f4c1cca build: add better-sqlite3 dependency
Adds better-sqlite3 to the Electron package dependencies
2026-07-28 12:56:17 +03:00
Bohdan Triapitsyn 72fa464910 feat: add Linux Electron release smoke builds
Adds an opt-in input to build Linux Electron artifacts in the smoke workflow
Builds and validates AppImage releases for x64 and arm64
Uploads Linux AppImage and update manifest artifacts
2026-07-28 12:41:50 +03:00
Bohdan Triapitsyn 3a5c399298 chore: bump @opencode-ai/sdk to 1.18.8 2026-07-28 12:26:20 +03:00
Bohdan Triapitsyn 1291cde5c2 feat(ui): sidebar redesign — project zones, grouping modes, full-page surfaces (#2480)
* checkpoint: flatten sidebar core (flat sessions, zones, single mode, folders flat)

* checkpoint: sidebar nav + scheduled/archive full-page surfaces, recent zone header

* checkpoint: worktrees management surface via project menu

* checkpoint: docs, i18n, validation for sidebar redesign

* checkpoint: unified row/zone geometry, recent backfill, tooltips everywhere, primary spinner

* checkpoint: branch icon marker, date moved to rich tooltip, recent back to pure time window

* checkpoint: PR state on branch markers + tooltip, no reserved right space, aligned show-more, instant tooltips

* checkpoint: reserve hover-action space so title text is not overlapped

* checkpoint: tighten hover-action reserve

* checkpoint: color-only unread emphasis to avoid title reflow

* checkpoint: drop new-subfolder action, folder actions overlay on hover

* checkpoint: fix sticky project headers (sticky on trigger div), stuck elevation

* checkpoint: full-bleed semibold zone headers, no top scroll fade

* checkpoint: headers without background tint (typography-only emphasis)

* checkpoint: recent header flush with scroll top (no pre-stick bump)

* checkpoint: shared tooltip provider with grouping (instant handoff between rows)

* checkpoint: blur pointer-click focus so hover chrome hides on mouse-leave

* checkpoint: tooltip closeDelay bridges inter-row gap

* checkpoint: nav above controls, merged view dropdown, project-scoped bulk selection, cross-worktree folders

* checkpoint: folder header tooltip with full path name

* checkpoint: true page surfaces (hidden chat, header title, close-on-select), multirun page, run-now jump, folders on top, controls row polish

* checkpoint: rename mode — dual-instance outside-click fix, no vertical shift

* checkpoint: session grouping mode toggle (by-worktree default, flat option)

* checkpoint: worktree header — hover padding reserve + delete worktree action

* checkpoint: frosted sticky header backing under desktop vibrancy

* checkpoint: align worktree sub-header with project header icon column

* checkpoint: restore worktree group DnD reorder; dense vibrancy header tint (Chromium mask+backdrop-filter)

* checkpoint: vibrancy — drop scroller mask so backdrop-filter samples rows (Chromium backdrop-root limitation)

* checkpoint: vibrancy headers use opaque sidebar tone (Electron transparent-window backdrop-filter bug)

* checkpoint: nav collapsed to one row (New session + surface icons), mirrors Add project row

* checkpoint: raise sticky zone headers above row action layers (z-20)

* checkpoint: New session as full-width CTA + single quiet toolbar row

* checkpoint: New session row back to quiet text form above the toolbar

* checkpoint: align toolbar left icon with New session icon column

* checkpoint: hoverless flat header controls (color-only hover states)

* checkpoint: sticky project headers toggle in view dropdown (default on)

* checkpoint: full-page surfaces adapted — archive directory filter panel, scheduled master-detail, multirun without duplicate title bar

* checkpoint: multirun joins mutually exclusive surface set

* checkpoint: surfaces leave via navigation only (no close/cancel buttons), robust close-on-new-session, drop dead MultiRunWindow

* checkpoint: no scheduled header description, aligned empty-worktree note, collapse/expand covers worktree groups

* checkpoint: overlay scrollbar above sticky zone headers

* checkpoint: archive delete icons reveal on hover with padding shift

* checkpoint: new worktrees surface at top of the worktree list

* checkpoint: no grab cursor on worktree headers

* checkpoint: worktrees page list-only with inline action, no worktrees in edit dialog, drop menu ellipsis

* checkpoint: worktrees page uses full content width

* checkpoint: worktrees header — 'in' instead of em dash, no description

* checkpoint: drop legacy OpenCode badge from worktree list, tooltip without OpenCode mention

* review: bound pr summary cache
2026-07-28 12:04:39 +03:00
Bohdan Triapitsyn 5787ea5d49 fix: resolve chat prompt read-only state for subagent sessions
Allows prompting subagent sessions without needing the parent record
Keeps subagents read-only when prompting is disabled
Covers root and subagent cases with unit tests
2026-07-28 02:50:02 +03:00
Bohdan Triapitsyn 3fa0c5be57 chore: bump @opencode-ai/sdk to 1.18.7 2026-07-27 23:13:21 +03:00
Bohdan Triapitsyn e2fa7dbad2 feat(ui): context panel 2.0 - surface rail, changes-first git view, live PR surface (#2418)
* feat(ui): add context surface registry and rail switcher

* feat(ui): move git and project notes into context surfaces, embed editor file tree

* feat(ui): replace right sidebar with context surfaces, per-surface panel widths

* refactor(ui): retire legacy main-tab overlays and right-sidebar state

* feat(ui): rail polish, right-docked file tree, terminal surface

* feat(ui): move terminal into context surface, per-surface tab closing, editor empty state

* feat(ui): tune default rail order and activity dot

* fix(ui): keep context panel controls anchored during width animations

* feat(ui): lazy-follow context panel resize with window-level drag tracking

* feat(ui): panel dividers, right-dock tree icon, muted outline folder icons

* feat(ui): restructure git view into changes-first surface with standalone PR surface

- Remove commit/update/pr tabs; git view is always changes + commit
- Promote pull request to its own rail surface with shared repo context
- Move update-branch and re-integrate flows into separate dialogs
- Add PR status chip and repo actions menu to the git header row
- Seed new PR-status entries from resolved sibling remotes to avoid
  a false "checking status" state when the PR is already known
- History/graph dialog refresh button, fingerprint global identity icon,
  muted outline folder icons follow-ups

* feat(ui): progressive-disclosure PR surface with live checks and pinned chat context

- Segment the PR surface into Overview / Checks / Comments pill tabs with
  live badges; merge controls move to the status row
- Live checks segment: progress bar, per-run rows with workflow names,
  elapsed timers, expandable failures, auto-refresh while pending
- PR comments and failed checks pin as chat-context drafts (like terminal
  selections) instead of sending an immediate message; works on new-session
  drafts too
- Shared prContext cache client+server, ETag conditional requests in the
  octokit wrapper (304s bypass rate limits), extended checks aggregate
  (inProgress/queued/startedAt)
- Resolve gh-CLI auth login for merge-permission checks
- Full-width description editor with matched control heights

* fix(ui): single source of truth for PR checks and status readers

- Derive the checks aggregate from the visible run list and sync it into
  the PR-status store so bar, badges, header, and git-view chip agree
- Route PR body hydration through the shared context cache
- Git-view PR chip reads the freshest entry across remote keys

* fix(github): freshness stamps prevent stale cache responses from regressing PR state

- pr/status and pulls/context responses carry a server-side fetchedAt that
  survives cache serves
- The status store rejects responses older than the held snapshot (only
  clearing the loading flag), and the checks sync adopts the context's
  stamp so stale status polls cannot flip fresher derived checks
- Regression test for the stale-response guard

* perf(github): repo-level pull-list cache collapses per-branch PR resolution

- One pulls.list per repo per state per 45s answers every branch (10
  worktrees = 1 call, not 10 query fans); in-flight fetches coalesce
- A complete repo list makes a no-PR miss authoritative, skipping the
  per-owner head queries AND the Search API fallback (the 30/min killer)
- force refresh bypasses the repo list cache; PR create/merge/ready
  invalidate it

* perf(github): back off Search API misses per repo+branch

A branch without a PR re-searched on every poll; with >100 closed PRs the
list miss is never authoritative, so the search fallback still ran and
burned the 30/min search quota. Remember misses for 10 minutes; PR
creation clears remembered misses for the repo.

* fix(github): dedupe re-run check runs to the latest per (app, name)

listForRef returns the superseded completed run alongside its re-run;
GitHub's UI shows only the latest per name. Mirror that in both pr/status
and pulls/context so counts and run lists match github.com.

* fix(ui): address review findings on registry test, surface docs, and PR-context keys

- Rail-order test asserts against the registry itself (was stale after the
  'pr' surface landed and failed)
- surfaces DOCUMENTATION.md describes actual behavior: has-content surfaces
  hide until content exists; only multi-instance/terminal panes are
  keep-alive, singleton surfaces remount and restore from stores
- PR-context cache keys are runtime-scoped JSON tuples; invalidation
  compares the directory exactly instead of by string prefix (+ test)

* fix(ui): wrap long unbreakable tokens in check-run details

Annotation messages with long SHAs/URLs overflowed the panel; break-words
on annotation title/message/rawDetails and output summary/text, and the
expanded run body clips instead of widening the panel.

* fix(ui): busy state for context-attach buttons and honest attach labels

- 'Attach failed checks' / 'Attach all to chat' show a spinner and disable
  while the context request runs (previously nothing happened for seconds)
- Action labels/tooltips reworded from send-to-agent to attach-to-chat
  semantics across all locales

* fix(i18n): Ukrainian attach wording uses 'прикріпити' with proper cases

* fix(ui): runtime-scope PR-view remote caches, correct surfaces doc on preview

- Remote/remote-url caches in PullRequestView are keyed by runtime +
  directory so a backend switch never serves another runtime's remotes
- surfaces DOCUMENTATION.md: preview is not keep-alive; preview tabs
  remount on switch like singleton surfaces

* fix(ui): rail active color, clearer collapse icon, remove dead bottom-terminal dock

Design-review feedback on the context panel:

- Context rail: icons enlarged 16px -> 18px; the active surface is now
  highlighted with the primary color only (no background, no scale
  animation), replacing the previous scale-up effect that read as a
  resize rather than a selected state.
- Files tree: the icon-only 'collapse all folders' toolbar button now
  uses collapse-vertical instead of contract-up-down, which was easily
  mistaken for a close button. The labelled 'Collapse all' dropdown item
  in the session sidebar keeps its icon since text removes the ambiguity.
- Terminal: removed the leftover bottom-dock expand/close buttons that
  rendered in the context-panel terminal but controlled a dock that no
  longer exists (nothing toggles it anymore), so the expand button
  appeared to do nothing and duplicated the panel-header fullscreen
  control. Cleaned up the entire inert layer with it: four useUIStore
  fields (isBottomTerminalOpen/Expanded, bottomTerminalHeight,
  hasManuallyResizedBottomTerminal), five actions, their persistence,
  the MainLayout resize listener that only served the dock height, the
  dock-driven refit effect in TerminalView, and the
  terminalView.bottomDock.* keys across all 10 locale dictionaries.

Validated: ui type-check and lint clean; messages parity test (2 pass)
and useUIStore contextPanel test (13 pass) green; icon sprite
regenerated via icons:generate.

* refactor: use PR visual state for git header icon

Derives the pull request icon color from a single visual state
Covers merged, closed, draft, blocked, and open PR states
Removes conditional class handling from the git header icon
2026-07-27 23:07:42 +03:00
Bohdan Triapitsyn 005b2e61b0 Composer: CodeMirror editor, unified prompt language, ChatInput decomposition (#2419)
* refactor(ui): unify composer @mention grammar

Extract the composer's @mention rule into composer/language/mentions.ts and
route highlighting, send-time extraction, backspace-deletes-the-mention and
the file-path check through it. The rule previously lived as four separate
regexes in ChatInput.tsx with divergent cleanup, so every new reference type
had to be taught to all four.

Bracket handling is now symmetric: [ and { were accepted before @ but ] and }
were not stripped from the tail, so [@plan] resolved to the name 'plan]'.

Add characterization tests for the markdown tokenizer, which had none, to
pin current behavior ahead of the editor migration.

* refactor(ui): unify composer slash, snippet and trigger grammar

Extract /skill, /command and #snippet scanning into
composer/language/prefixTokens.ts, and the rule deciding which autocomplete a
caret asks for into composer/language/triggers.ts.

Each sigil previously had three separate implementations: one for
highlighting, one for send-time collection, and one for opening the picker.
They disagreed on the valid character set — the send-time skill scanner
accepted only lowercase names, so a /My_Skill token was painted as a command
but never collected. Scanning is now generous and membership in the command,
skill or snippet registry is the authority.

resolveAutocompleteTrigger replaces the 90-line branch chain in
updateAutocompleteState with a pure function, keeping the previous
command > skill > snippet > mention precedence.

* refactor(ui): tokenize the composer in a single pass

Add composer/language/tokenize.ts as the one entry point producing every
highlight range from the text plus what the composer knows about the
workspace. It replaces six independent memos in ChatInput.tsx that each
re-scanned the same string for markdown, fenced code, mentions, slash tokens,
snippet tokens and attachment citations.

Mentions now distinguish the reference span from the raw token: 'see @a/b.ts,'
highlights @a/b.ts and leaves the comma as sentence punctuation, while
backspace still deletes the whole token so a wrapping bracket is not orphaned.

* feat(ui): add the CodeMirror composer editor

Add composer/editor: a CodeMirror view that renders the prompt language as
mark decorations, and a controlled React primitive around it.

The composer previously painted a transparent textarea over a mirror div,
which restricted highlighting to styles that do not change glyph advance
width -- so bold and italic were impossible and the overlay was disabled
outright on mobile, where wrapped text drifted from the caret anyway.
CodeMirror owns the text and the caret together, removing the second layer.
The document stays a plain string, so nothing downstream has to serialize a
rich model back into a prompt.

Split resolveHighlightSegments out of buildHighlightParts so the mirror
overlay and the editor decorations share one priority resolution.

Not yet wired into ChatInput. Editor rendering is unverified: the package has
no DOM test environment, so only the state-level extension is covered.

* refactor(ui): move the composer onto the CodeMirror editor

Replace the transparent-textarea-over-mirror-div composer with ComposerEditor.
The mirror is gone, and with it the constraint that highlighting may only use
styles which do not change glyph advance width, and the mobile carve-out that
disabled highlighting entirely because wrapped text drifted from the caret.

Removals the editor makes unnecessary:
- measureCaretInTextarea, 46 lines of hand-built text mirroring for popup
  placement, replaced by the editor reporting caret coordinates
- adjustTextareaHeight and its two layout effects, replaced by the editor
  sizing itself; the dictation transcript height is now an explicit floor
- getInsertedTextFromChange, which diffed old against new text to recover what
  a paste inserted; the editor reports the change directly
- the highlight mirror element, its scroll-sync and its parts memo
- a _commandMetadata property stashed on the textarea and never read

Caret placement no longer needs a requestAnimationFrame after a text edit:
text and selection travel in one transaction.

Runtime behavior is unverified -- the package has no DOM test environment.
Type-check, lint, the 295 chat tests and a production web build pass.

* refactor(ui): extract composer text and path helpers

Move the composer's text-splicing rules to composer/text.ts and its path
handling to composer/attachments/filePaths.ts, with tests. None of this logic
was covered before, despite handling VS Code drop payloads, percent-encoded
file URIs and Windows drive letters.

normalizeDroppedPath and toProjectRelativeMentionPath were useCallbacks that
closed over nothing but their argument and the search directory; they are now
plain functions taking the root explicitly.

* refactor(ui): make the composer's slash commands a table

Nine of the composer's local commands did the same thing -- render a visible
magic prompt plus synthetic instructions and send them as one message -- and
that shape was written out nine times as an else-if chain. Adding a command
meant copying twenty lines and remembering to change every string.

composer/submit/slashCommands.ts holds the commands as data and the shape as
one executor; ChatInput keeps only the five that manipulate session state or
open UI. The command names, prompts and failure toasts are now typed against
the magic-prompt and i18n key unions, so a mistyped key fails to compile
rather than failing at runtime.

Net: 257 lines of branching become 68.

* refactor(ui): move the composer's footer components out of ChatInput

RevertedMessageDock, ComposerAttachmentControls, PermissionAutoAcceptButton,
FocusModeButton and ComposerActionButtons each own a piece of the composer
chrome and were defined inline above the 4500-line component. They now live
under composer/ui with the imports they actually need.

Pure moves; getRevertedPreview travels with the dock, its only caller.

* refactor(ui): extract composer draft persistence

Move the draft lifecycle -- identity switching, debounced writes, external
deletion, and the flush-on-hide/freeze/pagehide edges -- into
composer/state/useComposerDraft.

It was seven interleaved effects and five refs sharing state through the
component body, which made the ordering constraints between them (skip the
next debounced write while restoring; record the empty signature before a
queued write can resurrect a deleted draft) invisible. They are now stated
where they apply.

* refactor(ui): extract drop payload inspection

hasDraggedFiles, collectDroppedFiles and collectDroppedFileUris were
useCallbacks with empty dependency arrays -- pure DataTransfer readers wearing
React clothing. They move to composer/attachments/dataTransfer.ts with tests
covering the host differences they exist for: browser File lists, VS Code's
proprietary tree types, OpenChamber's own internal-drag marker, and getData
throwing during dragover.

* refactor(ui): extract the mobile composer shell

Move the pill state machine into composer/state/useMobileComposerShell and
the visual-viewport pinning into composer/state/useMobileViewportPin.

Between them they held eight refs, four pieces of state and eleven effects
interleaved with the rest of the composer, which hid what they are: not one
state machine but a state machine plus a set of corrections for specific
platform behaviors -- mobile browsers dismissing the keyboard before a tap's
click lands, iOS refusing programmatic focus outside a gesture, WebKit leaving
the layout viewport panned after the keyboard hides, overlay chains handing
off through a frame where nothing is open.

Verbatim moves: every timeout, flushSync and guard keeps its value and its
reason, because none of them is verifiable outside a real device.

* refactor(ui): extract outgoing message assembly

A single send can carry queued messages, the composer text, inline review
comments, resolved @file attachments, a linked issue or PR, synthetic parts
from conflict resolution, and a skills instruction -- all flattened into
OpenCode's one-primary-plus-parts shape. The flattening rules were spread
through handleSubmit with no coverage, so the ordering they encode (oldest
queued message becomes primary; inline comments attach to the last authored
body, not a new part; PR instructions precede the diff) was trusted rather
than checked.

buildOutgoingMessage is a pure function over injected resolvers, with 25 tests
covering that ordering.

* refactor(ui): extract new-session draft targeting

Move project and worktree selection for the new-session draft into
composer/state/useDraftTarget.

The rule worth naming is that the draft can point at a directory that does not
exist yet -- a worktree still being created. It has to survive not appearing
in the branch list, or the selector snaps back to the project root mid-creation
and the session starts in the wrong place.

* refactor(ui): extract composer context chips and linked reference rows

The chips standing for attached-but-not-typed context (review comments, dev
server logs, preview annotations, terminal selections) were four near-identical
inline blocks; three of them collapse into one CountChip.

The linked issue and linked PR rows were 100 lines of duplicated markup
differing only in their number label, their branch line and which picker they
reopen. They are now one LinkedReferenceRow.

* refactor(ui): extract draft target selectors

Move the project and branch pickers into composer/ui/DraftTargetSelectors:
inline selects for desktop, trigger buttons and bottom sheets for mobile, all
rendering the same options from useDraftTarget.

The project label -- custom icon image, configured icon, or a folder fallback,
with the project name -- was a useCallback rendering JSX and used from four
places; it is now a ProjectLabel component.

* refactor(ui): extract the collapsed mobile pill composer

Move the pill into composer/ui/MobilePillComposer. Both places that ask
dictation to start now go through one toggleDictation callback rather than
dispatching the global event inline.

* refactor(ui): extract the composer footer

Move the footer row into composer/ui/ComposerFooter, which now owns the
desktop and mobile layouts and the components they place. ChatInput keeps only
the handlers it passes in.

* refactor(ui): collapse the composer's four autocomplete states into one

The composer tracked each picker with its own show flag and query string,
which encoded 'exactly one is open' as four booleans that had to be kept
mutually exclusive by hand. It is now one openAutocomplete kind plus one
query, which is what resolveAutocompleteTrigger already returns.

The four popup blocks -- identical apart from their component and caret width
-- become ComposerAutocompletePopups.

* refactor(ui): extract autocomplete positioning and message history

useAutocompletePosition owns caret-relative popup placement, which only
applies in focus mode.

useMessageHistory owns arrow-key recall. Its transitions are pure functions
with tests: entering history stashes the draft exactly once, so walking back
several messages and returning still restores what the user actually typed
rather than the last recalled message.

* docs: document the composer module

Record what each layer owns and the invariants that are not visible from the
code: that the prompt language is the single source of truth for syntax, which
ordering rules in the submit assembly and draft lifecycle are load-bearing,
that the mobile hooks are platform corrections rather than state machines, and
that rendering, focus, keyboard and WKWebView behavior are not covered by
tests and must be verified by hand.

* fix(ui): restore the composer caret colour and click-to-focus

Two regressions from the editor migration.

The caret rendered black in dark themes. CodeMirror's base theme hard-codes it
through '.cm-editor.cm-light .cm-content', one class more specific than the
plain '.cm-content' rule the composer theme used, so the base theme won.
Matching that specificity with '&.cm-editor' fixes both variants.

Clicking the composer's empty space no longer focused it. A textarea filled
its box, so the browser placed the caret for any click inside it; CodeMirror's
content element covers only the text. The content box now stretches to the
full editor height, and clicks landing outside it — in the composer's padding
— are forwarded to the nearest text position.

The theme moves to its own module with a test that installs it. EditorView.theme
compiles selectors at import and throws on scopes it was not given, including
'&light' and '&dark'; neither the build nor the type-check catches that, and
the failure takes the whole composer down at runtime.

* fix(ui): colour the composer caret where it is actually drawn

The previous fix styled caret-color, which drawSelection() overrides with
'transparent !important' at the highest precedence -- it hides the native
caret and draws its own .cm-cursor element, whose base style is a hard-coded
'border-left: 1.2px solid black'. So the caret stayed black on dark themes.

CodeMirror recolours that cursor only for editors that declare themselves
dark. OpenChamber themes are not merely light or dark, so the cursor takes the
surface foreground directly instead.

The theme spec is exported and asserted against: the caret rule must target
.cm-cursor, must not style caret-color, and must carry enough specificity to
beat CodeMirror's own &dark override.

* feat(ui): add emphasis, attention and path highlighting to the composer

The constructs the editor migration was for.

- **bold** and *italic* render as real weight and slant. They are additive
  styles: a segment carries one class string, so choosing between weight and
  colour would lose one of them -- bold inside a heading now keeps the heading
  colour and gains weight.
- '!!! ' marks an attention line. Three marks, so a sentence ending in '!!'
  is not swallowed.
- '~path' highlights a path without attaching it, unlike '@path'. Inert by
  design: it feeds neither the autocomplete nor the send path.

False positives are excluded positionally rather than by character, since
these delimiters are ordinary prose: '2 * 3' and 'foo_bar' are not emphasis,
'~approximately' and '~1.2 seconds' are not paths.

Also fix the expanded composer, which kept the collapsed composer's eight-line
height cap: the editor scrolled inside an invisible window while the rest of
the surface sat empty.

* fix(ui): style the composer's selection instead of leaving CodeMirror's

Selecting text rendered it in CodeMirror's stock lavender, which buried the
token colours. drawSelection() paints its own layer and CodeMirror styles the
focused case through a six-class selector; the composer's rule was three deep
and lost.

The tint is translucent rather than the flat selection token: an opaque
selection hides the colours the composer exists to show, and selecting text
here is for moving it, not for stopping reading it.

Same failure shape as the caret, so the theme test now covers both.

* fix(ui): mute the composer placeholder

The placeholder rendered at full text brightness. Its colour referenced
--surface-mutedForeground, but the theme emits --surface-muted-foreground:
an unknown custom property makes the declaration invalid rather than falling
back, and since color inherits, the placeholder simply took the editor's text
colour while the source looked correct.

The theme test now rejects camelCased tokens outright, since this failure is
invisible in every check that does not render.

* fix(ui): create the composer editor before the expand gesture ends

The mobile pill expands with flushSync and focuses the editor on the very next
line, still inside the tap's call stack, because that is the only way a mobile
browser raises the keyboard. The EditorView was created in a passive effect,
which flushSync makes no promise about — so at the moment focus() was called
there was no view to focus.

With a textarea the element existed as soon as flushSync returned, which is
why this worked before the migration.

Creating the view in a layout effect restores that ordering.

* perf(ui): keep the composer editor alive across the mobile pill swap

The pill and the full composer are different subtrees, so expanding or
collapsing unmounted and rebuilt the editor. With a textarea that was one DOM
node. A CodeMirror view is extensions, state, document, decorations and a
first measure — all inside the tap's flushSync, before the browser is allowed
to paint the swap. The shape change therefore landed late enough to look
driven by the keyboard rather than by the tap.

The view now lives in a store owned by ChatInput and is detached and
re-attached instead of destroyed and rebuilt. Its extensions read callbacks
through a ref held by the store, so a kept view always calls into the mounted
instance; compartments move to module scope, since per-instance ones would be
unknown to a reused view's configuration.

Also restore the caret hold: WKWebView draws the caret as a native layer that
ignores CSS transforms and visibly flies across the screen during the keyboard
slide. The rule hiding it targeted textarea and input, which the composer is
no longer — and its caret is now a drawn .cm-cursor element rather than the
native one.

* debug(ui): on-screen timeline for the mobile composer swap

TEMPORARY, Capacitor-only. Two plausible fixes for the swap lagging behind
the keyboard changed nothing, so the theory behind them was wrong. This
overlay draws the event timeline straight onto the screen -- the tap, the
committed swap, the first paints after it, the keyboard choreography, and
whether the editor was created or re-attached -- so one screenshot replaces
guessing. It doubles as an asset-freshness check: no overlay means the app
runs a bundle from before this commit.

* fix(ui): put the composer swap on glass before the keyboard moves

The overlay timelines settled it. The swap itself was never slow: commit in
12ms, editor re-attach in 3ms, focus immediate. What lagged was presentation:
WKWebView stops presenting web frames the moment focus starts the keyboard
transition and holds the last presented frame until it ends. Focusing in the
same task as the swap meant the last presented frame still showed the pill —
paint-1 fired at 29ms, the next frame at 190ms, exactly when the keyboard
was already moving.

On expand, Capacitor now waits two frames before focusing, so the swapped
composer is presented first and the keyboard rises under it. The Capacitor
WebView raises the keyboard for a focus() outside the gesture task; mobile
browsers do not, so they keep the synchronous path.

The collapse direction had a genuine race, caught on one screenshot: the
oc:keyboard-intent collapse arrives a few milliseconds after blur on a
setTimeout(0), and React's scheduling of setFocused(false) can lose to it —
busyRef stays stale, the intent handler skips the instant collapse, and the
pill appears via the 250ms fallback, 370ms after the keyboard has gone.
The Capacitor blur branch now commits the state with flushSync.

The diagnostic overlay stays in until this is confirmed on device.

* fix(ui): raise the keyboard from the swap's first frame

Two frames of delay before focusing made expand visibly sequential: swap,
then keyboard. Focusing inside the first frame after the commit puts the
swap's frame into the rendering pipeline before the keyboard transaction
starts, so the keyboard rises from the tap and the composer appears during
the rise rather than after it.

* fix(ui): restructure the draft screen in the same frame as the pill swap

The draft screen centers its title over the space the composer leaves, and
its starter chips leave when the keyboard is up. The chips were keyed on
oc-keyboard-open, which lands with the keyboardWillShow bridge event ~100ms
after the tap — so expanding the composer restructured the page twice: once
at the swap (composer grows, title re-centers) and again mid-keyboard-rise
(chips vanish, title re-centers again). Chat has no centered content, which
is why it was already smooth and the draft screen was not.

A root class now announces the expanded composer from a layout effect, in the
same frame as the swap, and the chips key on it: one restructure, fused with
the pill morph, before the keyboard moves. The keyboard classes remain as
fallbacks for keyboard-up states that do not go through the pill.

* debug(ui): remove the mobile swap timeline overlay

The diagnostic did its job: it identified WKWebView's presentation pause
during keyboard transitions, the React-scheduling race in the collapse path,
and the draft screen's double restructure — all fixed and confirmed on
device.

* feat(ui): grow the mobile composer with content, drop the fullscreen handle

The swipe-up handle promised a fullscreen composer but the normal eight-line
cap already reached within a line of the same height, so the gesture bought
almost nothing and cost a 28px bar above the editor.

The composer now just grows with what is typed: a generous line cap plus a
CSS ceiling of the space the keyboard actually leaves, whichever is smaller
(the editor cap accepts both and takes min()). The handle, its swipe
gestures and the shell's touch plumbing are gone.

* fix(ui): measure the mobile composer ceiling instead of estimating it

The 220px chrome constant guessed at what surrounds the editor. The old
fullscreen handle guessed at nothing — it let flex distribute real space (and
on Capacitor even that silently failed: its h-full resolved against a
shrink-wrap parent, which is why the gesture bought almost nothing).

The ceiling is now measured the way the handle meant to: the screen container
is marked data-composer-bound, and the editor may grow until the composer
fills it — chrome around the editor read live from the DOM, so attachment
chips, the model row and keyboard resizes all shift the cap by themselves.

* fix(ui): keep a 4px gap between the grown composer and the header

On the chat screen the fully grown composer's border landed exactly on the
header's bottom edge. The gap is a visual design choice, not another chrome
estimate: the ceiling itself stays measured.

* fix(ui): show iOS selection handles without giving up typing speed

iOS pins its selection drag handles to the visible native selection and
colours them from the caret, while drawSelection() hides both. Removing
drawSelection(), or leaving the native caret visible while typing, both
make iOS answer every keystroke with severe input lag. Touch devices now
keep drawSelection() and layer a theme over it that re-shows the native
selection, plus an .oc-native-range marker that enables the native caret
only while a range is selected — when there is no caret to lag on.

* feat(ui): make file mentions editable instead of atomic-delete

Deleting a character inside an @file mention edited nothing and erased the
whole token. Mentions now edit like /skill tokens: a deletion changes the
text and the caret position reopens the file picker on its own. findMentionAt
and MentionToken.rawEnd existed only for the atomic delete and are removed.

* fix(ui): composer selection visibility and external-insert caret

Selection was nearly invisible for two reasons: the tint was mixed down from
--interactive-selection, which themes define with its own alpha (often under
10%); and the painted selection layer sits behind the content, so tokens with
their own background (inline code, fences) covered it entirely. The native
selection now shows on every device, not only touch — it paints over token
backgrounds — and its tint comes from --primary at 25%, a full-strength
colour in every theme. drawSelection() and the range-scoped native caret stay
exactly as before, so typing keeps the lag-free path.

External rewrites (add-to-chat, draft restore, history, dictation) also left
the caret at its old position, so the next insertion landed inside the
previous one. They now put the caret at the end, as the old textarea did, and
pin the scroller to the bottom once the layout settles — a transaction-time
scrollIntoView fires before the max-height cap exists and scrolls nothing.

* fix(ui): render ***triple emphasis*** as bold italic

The emphasis tokenizer capped delimiter runs at two characters, so ***x***
parsed as a stray asterisk plus an italic span. Runs of three now emit both a
strong and an emphasis range over the same content; the two are additive
styles, so they compose into bold italic.

* fix: insert mentions through editor dispatch

Places the caret immediately after an inserted mention
Avoids rewriting the whole message and jumping the scroll to the bottom
Falls back to appending inline text when no editor is available
2026-07-27 22:21:38 +03:00
ChangeHow 8801d69c66 fix(ui): preserve Markdown code selections (#2449)
* fix(ui): preserve markdown code selections

* test(ui): clarify markdown selection fixtures

* fix(ui): preserve highlighted code languages

* fix(ui): preserve markdown block boundaries
2026-07-27 13:07:25 +03:00
pablogonzalez 8aaef3dec1 fix(desktop): dedupe Linux tray D-Bus calls to stop main-thread freeze (#2459)
The tray controller called setToolTip and setContextMenu unconditionally on
every snapshot push. On Linux both are synchronous D-Bus calls into
plasmashell's StatusNotifierItem host. useTraySync debounced pushes at 120ms
(up to ~8/sec during token streaming), so each push made 2 blocking D-Bus
round-trips even when only dockBadgeCount changed or a token streamed into
an already-listed session — i.e., tooltip and menu content didn't change.
The native block has no JS-level log, is intermittent (depends on plasmashell
load), and eventually freezes the Electron main thread until crash.

setTitle and setImage were already deduped; setToolTip and setContextMenu
were the gap. Mirror the existing lastX pattern:

- tray.mjs: dedupe setToolTip (exact string compare via lastTooltip) and
  setContextMenu (via lastMenuKey, a lightweight signature of menu-affecting
  fields: sessions/approvals/usage). menuKey is a string concat, cheaper than
  buildMenu itself, so skipping buildMenu when the key matches also saves
  work. Cache-after-call ordering matches lastTitle/lastTooltip so a throw
  forces a retry rather than skipping one. destroy() resets both new vars.
- useTraySync.ts: FLUSH_DEBOUNCE_MS 120 -> 500. Tray doesn't need sub-second
  updates; approvals are rare discrete events that flush through the debounce
  and the main app UI stays instant via SSE/stores.

The first change is the real fix; the second is a complement. Either alone
helps; together they eliminate the freeze under streaming.
2026-07-27 11:51:22 +03:00
ChangeHow e52d692ac0 fix(ui): stabilize Markdown code block line layout (#2444)
* fix(ui): stabilize markdown code block layout

* fix(ui): respect text range boundary affinity

* test(ui): cover trailing text range boundaries
2026-07-26 20:40:33 +03:00
Bohdan Triapitsyn b6cdf7b6a7 chore: update opencode sdk version to 1.18.5 2026-07-26 18:53:58 +03:00
Bohdan Triapitsyn f6fa00c45b chore: updated unreleased vscode changes 2026-07-26 18:51:34 +03:00
Bohdan Triapitsyn 0583df0c35 chore: updated changelog for unreleased changes 2026-07-26 18:50:16 +03:00
pablogonzalez e2b0a113b8 feat(quota): add Crof and NeuralWatt quota providers (#2415) 2026-07-26 17:57:53 +03:00
Kai Yang 023ec2362e fix(ui): compact embedded chat URLs (#2431)
* fix(ui): keep embedded session chat URLs compact

* test(ui): guard embedded session chat URL length

* fix(ui): sync embedded custom themes

Bootstrap custom themes from parent window so iframe URLs stay compact.

Refs #2423

* docs(ui): clarify theme sync precedence
2026-07-26 15:33:04 +03:00
Serhii Dziupin ae40f0fe7d fix(desktop): fix Linux window control order and drop auto position (#2434)
Left-side controls now use macOS traffic-light order (close, minimize,
maximize). Remove the unused auto option and default window controls to
the right for Windows and Linux frameless chrome.

Authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-07-26 13:39:46 +03:00
Bohdan Triapitsyn c9f673582a feat(ui): glide the underline tab indicator
Give the underline tab strips the same motion as the pill ones: the
indicator travels to the newly selected tab and leans toward a neighbour
on press, instead of jumping.

Opening, closing, and reordering tabs shift the indicator without a user
switch, so those repositions still snap.

Also drop the overshoot from the shared travel curve — both indicators now
decelerate into place rather than springing back.
2026-07-25 15:11:07 +03:00
Bohdan Triapitsyn aab9f3bca3 feat(ui): spring the active pill in tab strips
Give the active-pill tab strips the lifted, physical feel of a spring
capsule: the pill now travels with a slight overshoot instead of jumping,
leans toward a neighbouring tab on press, and sits on a raised surface with
a soft ambient shadow rather than a hard border.

Transitions are opt-in per switch so first measurement and container
resizes still snap into place, and reduced-motion disables them.

Also drop the icons from the git commit/update/pr switcher, along with the
now-unused iconOnlyActiveTab prop.
2026-07-25 15:05:28 +03:00
Bohdan Triapitsyn 248e403977 feat: accept wide chat layout setting in updates
Sanitizes wideChatLayoutEnabled when it is a boolean
Rejects non-boolean values for the setting
Covers the new behavior with tests
2026-07-25 11:21:21 +03:00
Bohdan Triapitsyn 57cfce9596 docs: update changelog for iOS notification support 2026-07-25 09:17:16 +03:00
Bohdan Triapitsyn 9f1bd0dfa0 fix: route APNs delivery per-token by registered environment
Issue: after defaulting APNs delivery to production (#2381), development
builds installed from Xcode stopped receiving notifications entirely:
their sandbox device tokens were sent to the production APNs endpoint,
rejected as BadDeviceToken, and dropped as dead.

Fix: the iOS shell reads the aps-environment entitlement from the
embedded provisioning profile and exposes it to the web layer as a
document-start user script (added in capacitorDidLoad, since Capacitor
replaces the userContentController after webViewConfiguration(for:)).
Token registration reports the environment to the server, which stores
it per token and groups delivery by environment for both relay and
direct APNs sends. OPENCHAMBER_APNS_ENVIRONMENT remains as an explicit
override forcing every send to one environment.

TestFlight/App Store builds and older clients without the field default
to production, preserving released behavior; the relay already accepts
env per send request.
2026-07-25 01:18:41 +03:00
Bohdan Triapitsyn fe0ef0d1da fix(ui): discard reverted branch on resend 2026-07-25 01:01:21 +03:00
Bohdan Triapitsyn 471010d127 docs: update changelogs with unreleased fixes and features
Added new CLI and managed-instance session capabilities
Documented chat, settings, shortcut, and mobile fixes
Noted small model, desktop, and iOS updates
2026-07-25 00:15:04 +03:00
Bohdan Triapitsyn d265a38365 feat: add toggle for showing draft starters on new sessions
Persists draft starter visibility across desktop and web settings
Adds a new OpenChamber visual setting with localization and search support
Hides the draft starter chips when the setting is off
2026-07-24 23:07:39 +03:00
Bohdan Triapitsyn 9ea1a2a728 fix(ui): Android soft keyboard support in terminal touch input
- summon the IME via Capacitor Keyboard.show() on tap-to-focus, since
  Android WebView only raises the keyboard for native tap-focus
- forward beforeinput text/backspace/enter to the terminal because
  Android IMEs deliver input as beforeinput with keyCode 229 keydowns,
  which ghostty-web ignores
- blur the focused terminal input and hide the keyboard before disposal
  so tearing down a focused editable mid-composition no longer wedges
  WebView touch dispatch (app froze on terminal tab close)
2026-07-24 22:48:59 +03:00
Bohdan Triapitsyn 16df9412fd fix: update keyboard shortcut abort logic
Use current session activity to determine when abort shortcuts are available
Prevent abort priming when there is no active session
2026-07-24 22:13:38 +03:00
Bohdan Triapitsyn e908db637b feat: agent and CLI control plane for sessions, worktrees, and scheduled tasks (#2408)
Add a shared OpenChamber control service with two thin adapters — a native
`openchamber` tool injected into managed OpenCode, and new CLI commands — so
users can manage parallel sessions, worktrees, and scheduled tasks
conversationally through agents or from the terminal.

Control plane:
- New openchamber-control service owning a fixed action contract:
  projects.list, models.list, session list/create/send/fork/status/messages,
  and schedule list/create/run/delete/toggle. Session and worktree deletion
  and project registration are deliberately not exposed.
- New openchamber-sessions module owning create/worktree/prompt orchestration,
  Goal Mode dispatch, wait semantics (initial idle never counts as completion;
  timeout and cancellation are failures), and explicit partial-failure results.
- Scheduled-task logic extracted into a service shared by routes, CLI, and the
  agent tool.

Agent tool:
- Managed OpenCode gets a materialized plugin registering one typed tool with
  a loopback-only callback, per-child ephemeral bearer (timing-safe, never
  persisted or logged), and abort propagation into the service.
- The ~1.5k-token schema applies progressive disclosure: short descriptions,
  server-side validation returning actionable usage errors, and intent
  guardrails — created sessions/tasks are user-facing work (not age
  self-delegation); worktree/goal/agent/variant/wait are omit-by-default;
  dispatches produce no completion notification, and later result r
  to session.messages, which now returns the authoritative sessionStatus.
- session.create without a user-named model picks from favorites/re
  send/fork omit the selection and the service reuses the target session's
  last user-message model, agent, and variant before falling back t
- An "Agent control tool" setting (default on, Save + Reload to apply)
  disables plugin injection entirely.

CLI:
- New `openchamber session`, `schedule`, `projects`, and `models` commands
  with automatic instance targeting, --wait/--timeout/--last-assist
  worktree flags, and Goal Mode, preserving interactive, non-TTY, --quiet,
  and --json contracts. The control HTTP timeout derives from the w
  instead of the 4-second default.

UI:
- New built-in "Schedule a Task" starter (/schedule-task) running a
  dialogue that defines a task and offers to create it via the tool after
  explicit confirmation; Craft a Goal and Feature Planning gain the
  handoff offer, and guided starters reserve the question tool for concrete
  option choices. Localized in all 10 locales, migrated into custom
  starter lists, hidden on VS Code.
- Sidebar shows CLI/agent-created sessions live via the control eve
- openchamber tool calls render with per-action titles and metadata.
2026-07-24 21:54:28 +03:00
Serhii Dziupin 484fe8bc18 fix(ui): move window controls position setting to Appearance (#2406)
* fix(ui): move window controls position setting to Appearance

Keep desktop chrome layout with other visual settings; General retains
startup, tray, network, and access controls. Update settings search page
mapping so the control still highlights correctly.

Authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* fix(ui): align Linux window controls and stop actions overlap

Size left-side window controls to match the titlebar icon row and drop
negative margins so the reserved titlebar width stays accurate. This keeps
the project-actions chevron from colliding with the New session title.

Authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* fix(ui): stack window-controls description above chips

Show the Appearance window-controls helper copy full-width under the
section title, with Auto/Left/Right chips beneath it.

Authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

---------
Authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-07-24 21:27:38 +03:00
Bohdan Triapitsyn ea157c9aa8 fix: force auto scroll behavior in virtualized message list navigation
Removed smooth scroll option to prevent offset issues on unmount
Ensured consistent auto-reconciliation for variable-height rows during scroll
2026-07-24 20:59:10 +03:00
18b58bdb6b feat(desktop): Linux AppImage releases and desktop feature parity (#2398)
* feat(electron): add Linux AppImage releases

* ci: cache Linux OpenCode CLI artifacts

* fix(ci): await Linux release inventory check

* fix(electron): add frameless window controls on Linux desktop

Linux AppImages were created without native WM decorations and without
in-app controls, leaving users unable to close the window with a mouse.

Treat Linux like Windows: frameless BrowserWindow plus the existing
WindowsWindowControls header buttons and app-menu entry. macOS keeps
hidden title bar with traffic lights unchanged.

Shared usesFramelessElectronChrome() helper drives main window, mini
chat, header insets, and titlebar controls.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* Linux desktop feature parity: Open in, background start, tray, multi-window (#2392)

* feat(electron): Linux parity for Open in, background start, and tray

Enable Linux desktop feature parity with macOS/Windows: open projects in
the default file manager and discovered apps, XDG autostart with
--background launches, system tray (including minimize-to-tray), and
tray sync from the renderer.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* fix(electron): allow packaged UI protocol navigations on Linux

Prevent openchamber-ui:// navigations from being handed to
shell.openExternal, which fails on Linux and blocked desktop UI flows.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* fix(ui): surface Linux tray settings in settings search

Include isLinux in settings search runtime context so minimize-to-tray
is discoverable on Linux desktop, matching Windows search behavior.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* fix(electron): stop Linux AppImage Waiting-for-OpenCode flicker

Sync updated boot-outcome init scripts to all BrowserWindows after
desktop_hosts_set, and prefer state.initScript on dom-ready so chooser
reloads inject local/ok instead of a stale not-configured outcome.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* fix(desktop): restore Linux AppImage updater feed and error UX (#2396)

Treat missing latest-linux*.yml (404) as no update available instead of a
hard failure, and stop swallowing updater capability/download errors in the
desktop bridge so About/sidebar can show actionable messages.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* docs: credit Linux AppImage contributors in changelog

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

---------

Co-authored-by: jibanez-staticduo <staticduo@gmail.com>
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-07-24 09:59:32 +03:00
Jakub Syty ddbb3c1db0 fix(web): honor Copilot model endpoints (#2382)
Route small-model requests through each Copilot model's advertised Messages, Responses, or Chat Completions endpoint.
2026-07-23 22:48:18 +03:00
Bohdan Triapitsyn 38222d07c8 fix: include active root sessions in Recent sidebar
Recent now shows active root sessions immediately, even if they are older than 48 hours
Archived sessions and subtasks still stay out of Recent
Added coverage and documentation for the new recency rules
2026-07-23 16:46:20 +03:00
Bohdan Triapitsyn 89f7c37d60 fix: make session ordering follow activity lifecycle
Session lists now promote a conversation when it starts working and again when it settles, instead of reacting to every streaming timestamp update. This keeps ordering responsive without bringing back the sidebar churn removed by the recent performance work.

Apply the same user-visible order across Recent, project and worktree groups, session switchers, mobile navigation, widgets, the command palette, and the desktop tray. Preserve pinned priority, freeze timestamp fallback ordering, and keep child-session activity scoped to siblings under the same parent so it never moves the root conversation.

Seed reconnect snapshots without synthetic jumps, clear ephemeral ranks on deletion and runtime changes, and cover lifecycle transitions, mixed root/child trees, metadata-only updates, and project-group ordering with regression tests.
2026-07-23 15:45:53 +03:00
Bohdan Triapitsyn dac8166937 fix: restore reproducible Docker dependency install 2026-07-23 13:34:53 +03:00
natheihei 12e2c55171 fix: default APNs delivery to production (#2381)
Issue: TestFlight iOS clients register production APNs device tokens, but notifications from a default OpenChamber server are not delivered.

Expected behavior: the released iOS app receives notifications without requiring users to configure an APNs environment, while development builds can explicitly select sandbox delivery.

Root cause: both relay and direct APNs delivery defaulted to the sandbox environment, so production TestFlight tokens were sent to the wrong APNs endpoint.

Fix: default both delivery modes to production, preserve OPENCHAMBER_APNS_ENVIRONMENT=sandbox as an explicit development override, and update tests and documentation.
2026-07-23 13:21:40 +03:00
Bohdan Triapitsyn a1784b8697 fix: load skip-local-server flag from shell env at runtime
Reads OPENCHAMBER_SKIP_LOCAL_SERVER after inheriting the user shell environment
Applies the flag consistently during startup and initial URL resolution
Updates Electron docs to explain where the setting comes from
2026-07-22 20:30:50 +03:00
Bohdan Triapitsyn f8b41076e6 chore: remove release repair workflows 2026-07-22 19:19:32 +03:00
Bohdan Triapitsyn 0b08641094 fix: finalize repaired releases 2026-07-22 19:14:36 +03:00
Bohdan Triapitsyn 2dcbbebf4e fix: build macOS x64 releases on Intel 2026-07-22 18:59:56 +03:00
Bohdan Triapitsyn 5f4ad9d824 release v1.16.3 2026-07-22 18:14:11 +03:00
Bohdan Triapitsyn 1b43c3cfeb docs: refine changelog highlight ordering guidance 2026-07-22 17:56:48 +03:00
Bohdan Triapitsyn 18e2e478d2 docs: reorder Windows SSH changelog entry 2026-07-22 17:55:14 +03:00
Bohdan Triapitsyn f26557701c fix: support Windows SSH remote instances 2026-07-22 17:48:12 +03:00
Bohdan Triapitsyn 32df7e1997 fix: require question tool for clarifying prompts 2026-07-22 16:46:57 +03:00
Bohdan Triapitsyn 1ba3807df5 fix: hide duplicated bash permission pattern
Removes permission patterns already shown as the rendered bash command
Keeps distinct patterns visible in the permission card
Adds tests for the pattern filtering behavior
2026-07-22 16:40:42 +03:00
Bohdan Triapitsyn 63d37eecdf fix: map PR summaries to directory branch keys
Preserves a stable lookup key for PR status summaries
Displays PR indicators using directory and branch labels
Skips unmatched summary entries safely
2026-07-22 16:34:25 +03:00
Bohdan Triapitsyn de6fa36960 docs: update unreleased changelog entries 2026-07-22 16:22:28 +03:00