Commit Graph
24 Commits
Author SHA1 Message Date
Mohamed BoudraandKassam Khoja 98559bf729 fix(app): release iOS sidebar rows after an interrupted drag (#7159)
An iOS pan waiting for manual activation can deliver finger-up without finalizing, leaving the row active and the outer scroller locked. Fail a pending pan when its touch stream ends so the existing finalization path cancels the drag. Track activation immediately so an active drag keeps its normal completion path.

Based on the touch-release diagnosis in #5586. The native regression fails before the fix and passes on the Namespace iOS simulator.

Co-authored-by: Kassam Khoja <kassam@shopwrk.com>
2026-10-16 22:30:27 +07:00
Mohamed Boudra eb1a8a2ec7 fix(markdown): one renderer on react-native-enriched-markdown for iOS, Android and web (#6973)
* fix(app): group native assistant prose into selectable runs

* fix(app): render selectable native prose with enriched markdown

* docs: explain native prose selection boundaries and tradeoffs

* fix(app): preserve markdown semantics and render limits in prose runs

* docs: record native prose compatibility fixes and heading limits

* fix(app): allow additive task lists and admonitions in native prose

* fix(app): preserve inline image formatting and reference scope

* fix(app): preserve markdown source and file actions in native prose

Prepare original source blocks with source-position edits from the assistant parser. Keep reference scope and live/history projection consistent, and retain link text when dispatching workspace file actions.

* fix(app): preserve native prose entities and links while bounding preparation cost

* fix(app): preserve provisional syntax at cached reveal boundaries

* fix(app): bound prose append work and preserve markdown edge cases

* fix(app): preserve home paths and normalized prose links

* fix(app): preserve parser-owned links and streaming tilde tails

* fix(app): keep growing markdown on the streaming block renderer

* fix(app): preserve bracketed IPv6 links in native prose

* fix(app): skip prose classification for blocks over the render cap

* fix(app): share selectable native prose across Markdown surfaces

* fix(app): preserve Markdown containers before grouping prose

* revert(app): remove shared prose-run renderer changes

Reverts ab4737bb2 and ac47e1063 before replacing the Markdown engine across all platforms.

* feat(markdown): use enriched renderer across all platforms

* test: verify library image controls and isolate website config

* fix(markdown): preserve file links and image revisions

* fix(markdown): restore diagrams and preserve link origin

* fix(markdown): retain diagram identity and reference scope

* fix(markdown): avoid empty web history measurements

* fix(markdown): preserve authored empty link titles

* test(markdown): exercise find across whole-message history

* fix(markdown): preserve encoded image paths after rebase

Keep the shared renderer image normalization and file-read regression coverage that originally lived in the main integration merge.

* fix(markdown): derive table and task list colours from the theme

* fix(markdown): restore selection highlight on web

* fix(markdown): restore file link hover tooltips on web

* fix(markdown): normalize inline HTML before rendering

* fix(markdown): create renderer types when applying package patch

The published nightly omits both MarkdownRenderers type files. Represent them as new files so patch-package succeeds on a fresh npm install, matching the already patched development package.

* fix(markdown): keep the renderer tree stable while images resolve

* fix(markdown): size inline images to the line and collapse failed loads

* fix(markdown): normalize only parsed HTML nodes

* fix(markdown): keep the first image reference definition

* fix(markdown): preserve complete assistant messages on native

* fix(markdown): classify web links explicitly and complete renderer types

* fix(markdown): preserve spacing between HTML elements

* fix(markdown): normalize HTML following code elements

* fix(markdown): preserve literal image fallback labels

* fix(markdown): retry transient image decode failures

* docs(markdown): record native selection and image limitations

* fix(markdown): preserve first definitions across Mermaid segments

* fix(markdown): reserve pending images and harden web rendering

* test(markdown): cover state retention during image arrival

* fix(markdown): preserve reader position while responses grow

Keep the same message component boundary when a live row becomes retained history, preserving Mermaid runtimes and selection nodes. Observe paced child text commits at the web viewport so sticky-bottom follows before the next frame.

* test(markdown): require growth while holding a streaming paragraph

* test(markdown): isolate image arrival from reload positioning

Mounted row totals change as history virtualizes, so observe the new response and tool directly. Hold the real PNG response after reload until the reading position settles, then verify its reserved height and viewport instead of attributing initial bottom-follow positioning to image decoding.

* fix(markdown): preserve adjacency around inline HTML

* fix(markdown): keep summary block boundaries outside bold text

* perf(markdown): scan HTML block boundaries incrementally

* test(markdown): gate delivery while establishing a held reader

* test(markdown): name shared renderer setup and file actions

* test(markdown): assert linear HTML boundary work without timing

* test(markdown): check large HTML table output without observing built-ins

* fix(markdown): restore accentBright link colour

* fix(markdown): require letters and known extensions in bare path recognition

* fix(markdown): match main's blockquote, bullet and h6 colours

* fix(android): keep inverted chat still during text selection

React Native decomposes Android list inversion into a 180-degree rotation with positive scaleY, so the existing visibility-scroll guard never recognized the inverted viewport. Inspect the composed matrix and reproduce the actual list transform through the native view manager in regression tests.

* fix(markdown): linked inline code keeps inline code size

* fix(markdown): reserve a placeholder box with an image glyph while images load

* fix(markdown): bare prose paths need an extension, line suffix or depth

Slash-separated English pairs were recognized as file links in prose. Require stronger path intent there while preserving the existing inline-code recognition rule.

* fix(markdown): horizontal blocks own their drags on compact layouts

* perf(markdown): keep native streaming updates in the unfinished tail

Native markdown reparses each updated view in full. Keep parser-closed blocks in a memoized head during paced reveal, then reunite completed prose for cross-paragraph selection. Preserve trailing spacing and source indentation at the split.

* perf(markdown): reuse Android measurements across view mounts

* test(markdown): bare Fabel/model and daemon/client stay plain text when copied

a502cfd0d stopped linking bare two-segment prose paths; the clipboard
expectation still carried the links from the earlier loose pattern and
failed on CI (playwright shard 1/4 on 60b4e2d05). main expects the plain
text, so restore that.

* perf(app): limit native chat mount batches

Whole-message Markdown rows measure synchronously during Fabric layout. Render fewer history rows per batch and retain a smaller offscreen window so opening a chat does not measure twelve history rows at once.

* fix(app): keep populated markdown in sync with native layout

Fabric can collapse streaming blocks before asynchronous rendering replaces the old text, producing a blank frame. Commit populated-view updates synchronously while retaining worker preparation for first mounts to avoid moving large history parsing onto the UI thread.

* perf(app): preserve unchanged sidebar workspace projections

Reopening a retained sidebar rebuilt its entry collection and invalidated every project row despite unchanged workspace data. Preserve collection identity as well as row identity, while keeping membership and ordering changes observable.

* Revert "perf(app): preserve unchanged sidebar workspace projections"

This reverts commit a3919faed4.
2026-10-16 21:03:37 +07:00
Mohamed BoudraandClaude e5124b039a Put sheets on one stack and show an agent's facts in its sheet (#6958)
* Put sheet tabs on the one sheet stack

Sheets are one primitive with one stack. A sheet opened from any sheet
stacks on top of it, and closing it uncovers the sheet underneath. The
compact workspace's file and subagent sheets were an in-tree Gorhom
BottomSheet with their own stacking, so a subagent chosen from the
subagents list sheet replaced the list instead of covering it.

- SheetHostScope: a subtree hosts the sheets presented inside it, so the
  sheet tabs draw inside the compact column and read the workspace
  providers while still joining the app-wide stack. Needs Gorhom's
  BottomSheetModalInternalProvider, which the patch exports.
- IsolatedBottomSheetModal takes enableDismissOnClose; false keeps the
  content mounted through a close so the unsaved-changes guard can veto
  it. Its backdrop then closes the sheet rather than dismissing the top
  modal. The Gorhom patch also keeps a dismissing modal's status through
  its final onChange(-1), without which such a modal unmounting while
  presented never left the portal.
- SheetTabs (was WorkspaceSheetHost) renders one IsolatedBottomSheetModal
  per store entry. The store never reorders sheets; a reveal of a covered
  sheet drops the unmodified sheets above it.
- Subagent rows that open a sheet keep the list open (closeOnSelect
  false); rows that open a tab still close it.
- A menu closes when its retained panel goes inactive, instead of hiding
  and reappearing with the panel on the next tab switch.

Co-Authored-By: Claude <noreply@anthropic.com>

* Show an agent's model and thinking in its sheet, and fit Open as tab in the header rail

A Paseo agent shown in a sheet has no composer, so the facts the composer's
model and thinking controls would show now sit in one line under the sheet
header, the same row provider subagents already get from the daemon. The line
resolves exactly as the controls do (resolveAgentModelSelection), from the
session agent and the providers snapshot, and shows wherever the pane host is
a sheet.

Open as tab becomes the full-screen glyph beside Close, drawn by the shared
SheetHeaderIconButton, so the title keeps its width. The tab switcher sheet is
titled "Workspace tabs" so it reads as what it lists.

Co-Authored-By: Claude <noreply@anthropic.com>

* Show mode and context usage in the sheet facts line

The line under a sheet's header now carries everything the composer's
controls and context meter show: model, thinking, mode, and used/max context
tokens with the share, each resolved as its control resolves it.

Co-Authored-By: Claude <noreply@anthropic.com>

* Re-render subagent rows when the workspace's tabs change

A row decides at render time whether choosing it opens a sheet or reveals
a tab, and keeps the list open only for a sheet. The hook behind it only
subscribed to the layout mode, so a row rendered while its agent was a
full tab kept closing the list after that tab was gone: on iOS, promoting
a managed child, closing its tab, and reopening it from the list dropped
the list under the sheet. Subscribe to the workspace's layout too.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-15 12:16:45 +07:00
Mohamed Boudra 73390346ca fix(ios): address clipped chat and Markdown text (#6445)
* Revert "fix(app): reflow iPad chat text after pane resize (#5716)"

This reverts commit 5715b5ba62.

* docs(changelog): describe iOS text clipping fix for 0.11.2

* chore(release): cut 0.11.2
2026-10-09 16:35:43 +01:00
Mohamed Boudra 5715b5ba62 fix(app): reflow iPad chat text after pane resize (#5716)
The selectable UIKit text view was a sibling of the Fabric content container, so existing chat rows kept an old width after Explorer changed the available pane. Make the container own the text view and preserve layout notifications on bounds changes. Includes a native geometry regression.
2026-09-30 13:56:29 +01:00
Mohamed Boudra 5d70ab2ab8 fix(app): restore character-paced streaming replies (#5013)
* fix(app): restore character-paced streaming reveal

Restore the v0.8 character reveal model and remove the word-level fade implementation so streamed assistant and reasoning text remains opaque, grapheme-safe, and immediately completes when a turn ends.

* fix(android): preserve inverted timeline selection

React Native's prebuilt Android library does not consume source patches under node_modules. Register the compiled scroll manager before the core package so inverted child-reveal calculations leave focus and text selection in place while normal and explicit scrolling remain unchanged.
2026-09-17 18:32:12 +02:00
Mohamed Boudra 7178583612 Stop Settings navigation from retaining closed views (#4228)
* fix(desktop): stop retaining closed Settings views

Settings rotations left detached detail panes reachable from the web style registry's strong element sets. Backport the upstream weak cleanup design and cover the real Electron bundle with a forced-GC regression.\n\nKeep the established sibling-route topology; compact Back relies on route-name identity.

* test(app): synchronize Mermaid completion layout assertion

The streaming acceptance test can observe the intentional completion remount between visibility and layout sampling. Consult the existing completion promise around that measurement so the transition is not reported as diagram loss.

* test(app): isolate worktree restart from provider startup
2026-09-02 21:27:30 +02:00
Mohamed Boudra 63923fdd07 Keep Android timeline still when tapping text (#4090)
* fix(android): keep timeline still when tapping text

Selectable Android text takes touch focus. React Native then scrolls focused children using untransformed coordinates, which moves inverted timelines even though the user did not scroll.\n\nSkip that auto-scroll only for touch-focused, selectable, non-editable text in inverted vertical scroll views, preserving normal focus navigation and editable inputs.

* test(e2e): isolate PR pane fixtures from bots
2026-08-31 12:10:40 +02:00
Mohamed Boudra 6c813d1a91 fix(app): keep chat synchronized with keyboard (#4044)
* fix(app): keep chat synchronized with keyboard

Dock the complete chat surface with worklet-driven layout padding so visual and native touch geometry move together. Reset cleared multiline native inputs and keep autocomplete portals anchored to the docked composer.

* fix(android): tolerate detached composer inputs

Return the nullable base input connection during IME restart instead of force-unwrapping it after the composer has blurred or unmounted.
2026-08-29 21:27:41 +02:00
5123b23f41 fix(app): stop a string SVG transform from crashing the app (#3711)
* fix(app): stop a string SVG transform from crashing the app

A root <Svg> is a real React Native view, so react-native-svg rewrites a
*string* `transform` into RN style syntax in `extractTransformSvgView`,
using a PEG parser whose grammar only accepts SVG transform functions.
Upstream lets that parser's SyntaxError escape, so any string it does not
understand takes the whole app down during render:

  SyntaxError: Expected transform functions but "n" found.
  "found": "n", "location": { "start": { "offset": 0 } }

`none` reproduces that message character for character; so does any other
CSS value (`rotate(-90deg)`, `translate(10px, 0)`). Every sibling
extractor in that file already catches the same error and degrades, so
the asymmetry is the bug: a string that is merely ignored on a <Path>
crashes on an <Svg>.

We feed that path untrusted input. The daemon scans a project for
favicon.svg / icon.svg / logo.svg (packages/server/src/utils/project-icon.ts)
and ships it to the client, where ProjectIconImage hands it to SvgCss.
Running the library's own XML parser over `<svg style="transform:none">`
yields exactly `transform: "none"` on the root, which is why the crash
starts once a host connects and the project icons render, with no
terminal involved.

Note this is *not* reachable from an RN-style array transform: the
extractor returns arrays untouched and only parses strings, so
`style={{ transform: [{ rotate: "-90deg" }] }}` (the context window meter
and the checks ring) was never a factor.

Patch the three builds a consumer can resolve - Metro takes the package's
`react-native` field (src/*.ts), Node and Vitest take main/module - and
register react-native-svg with the postinstall patch runner so the patch
actually applies. The new test pins all three.

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

* test(app): cover parsed root SVG transforms

Exercise the dependency patch with root props produced by the library's XML and CSS parsers, including embedded style rules that can arrive through project icons. Keep dependency-patch documentation focused on the reusable install and resolution constraints.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-08-23 11:05:25 +02:00
Mohamed Boudra 4391c47615 fix(app): clear composer text input upon sending on mobile (#3564)
* fix(app): clear mobile composer input on submit

Submitting a prompt on mobile failed to clear the native text input because EditingTextInput.replaceText previously only called setNativeProps({ text }), which is ignored on Fabric and PasteTextInput.

Fix this by:
- Delegating replaceText in EditingTextInputNative to inputRef.current.replaceText when available, clearing via clear() when text is empty, and falling back to setNativeProps.
- Patching @mattermost/react-native-paste-input to expose replaceText(text, selection) backed by native Commands.setTextAndSelection, updating clear() to use live event counts, and preventing useImperativeHandle closures over null refs during initial mount.
- Registering the patch in scripts/postinstall-patches.mjs and adding unit tests for native text input replacement and clearing.

* fix: update nix npm dependency hash
2026-08-19 18:53:26 +02:00
Mohamed Boudra f55f6d0599 fix(app): avoid draggable-list gesture livelock
Manual activation now runs once while the recognizer is waiting; reactivating or failing an already-active recognizer could leave the gesture pipeline stuck.
2026-08-17 21:15:17 +02:00
Mohamed Boudra 90c97c1b1a refactor: standardize status badges and fix composer whitespace handling 2026-08-16 19:14:04 +02:00
Mohamed Boudra 0ba7251280 Revert "Make large diffs open quickly and scroll as one surface (#3212)"
This reverts commit b599d38a77.
2026-08-12 15:29:21 +02:00
Mohamed Boudra b599d38a77 Make large diffs open quickly and scroll as one surface (#3212)
* perf(app): virtualize large diff rendering

Render bounded diff row groups through one vertical list and one horizontal surface. Bound retained list ranges when expansion grows the data set so large diffs paint promptly without sacrificing scrolling behavior.

* refactor(app): normalize working tree diff options

* fix(app): keep wide diff text reachable
2026-08-11 12:18:48 +02:00
Mohamed Boudra 3778d0ad7b Fix mobile sidebar swipe and drag failures (#2709)
* fix(app): prevent text selection during Android swipes

Gesture interception did not cancel the native view where the touch began, leaving Android's pending text-selection action alive.

* fix(app): restore iOS sidebar close swipes

Fabric could fail to attach close handlers when their target lived inside retained but display-hidden panel content. Keep a registered native gesture host mounted outside the presentation wrapper so close swipes work over nested lists without activating rows.

* fix(app): recover sidebar drags after cancellation

Cancelled native drags could leave a lifted row and the shared outer list locked. Scope terminal cleanup to the owning gesture session and release the lock on cancellation or unmount.

* test(app): clean up partial drag fixtures
2026-08-01 04:05:54 +08:00
Matt Cowger 9ea58aae0c fix(server): stop OpenCode daemon crash on session close, fix #2014 (#2027)
* fix(server): patch OpenCode SDK unhandled rejection on stream abort

The SDK's SSE abort handler calls reader.cancel() without handling
its rejection. When Paseo aborts the event stream during normal
session close, that promise can reject and crash the daemon with an
unhandled rejection outside any Paseo-owned code path.

Patches both copies the SDK ships (dist/gen and dist/v2/gen, the
latter being what Paseo actually imports). Extends
postinstall-patches.mjs to support patch-package running from a
non-root cwd, since @opencode-ai/sdk lives in packages/server's own
node_modules rather than the hoisted root.

* fix(server): stop assuming OpenCode's default agent is "build"

OpenCode users can rename or delete any agent, including the built-in
"build"/"plan" defaults. Paseo was injecting a hardcoded "build"
agent in several places whenever no mode was explicitly requested:

- The protocol manifest declared defaultModeId: "build" for opencode,
  which AgentManager.normalizeConfig applied to every session
  (including internal metadata-generation sessions) before any
  provider-level logic ran.
- OpenCode's own normalizeOpenCodeModeId defaulted an empty/"default"
  modeId to "build" rather than omitting the agent field.
- Unattended child creates (e.g. subagents spawned by an unattended
  parent) forced modeId: "build" to express unattendedness, even
  though that's already carried by the auto_accept feature.

Now an unset mode stays unset end-to-end: the "agent" field is
omitted from OpenCode prompt/command calls entirely, letting OpenCode
fall back to its own configured default agent instead of Paseo
guessing one that may not exist.

* fix(server): validate agent create mode on the WebSocket session path

resolveAndValidateCreateAgentMode already rejects modes unknown to a
provider's discovered mode list, but it was only wired into the MCP
create path. App-created agents (create_agent_request) skipped it
entirely, so a client's remembered mode preference — which can go
stale when a user renames or deletes OpenCode agents — sailed through
to the provider and failed mid-turn with an opaque error instead of a
clear rejection at creation time.

resolveSessionCreateAgent now calls
providerSnapshotManager.resolveCreateConfig, matching the MCP path,
so an invalid mode now throws 'Invalid mode ... Available modes: ...'
immediately on create.

* chore: address review feedback on create-agent and postinstall script

- Document the cleanup-ordering constraint in resolveSessionCreateAgent:
  mode validation runs after buildSessionConfig (cwd isn't known until
  that completes), so a thrown validation error leaves any
  worktree/workspace buildSessionConfig created for the caller to clean
  up. session.ts already handles this for the worktree path; the
  directory-only workspace path has a pre-existing gap, not introduced
  by this validation.
- Log spawn errors from patch-package in postinstall-patches.mjs
  (e.g. ENOENT if it's missing from PATH) instead of failing silently.

* fix(server): don't fabricate OpenCode modes when discovery finds none

fetchModesFromClient and mergeOpenCodeModes fell back to
DEFAULT_MODES ([build, plan]) whenever OpenCode discovery returned
nothing — e.g. for a freshly-created worktree whose OpenCode server
hasn't loaded the project config yet. That fabricated list let
create-time mode validation accept a stale 'plan' preference that the
provider then rejects at prompt time (Agent not found: 'plan').

Return an empty list instead: OpenCode users can rename or delete any
agent, so any hardcoded fallback risks validating a mode that doesn't
exist. DEFAULT_MODES is retained only for description enrichment and
sort ordering.

* fix(app): reconcile stale selected mode against discovered modes on create

The mode picker displays modeOptions[0] when the stored modeId isn't
in the discovered list (e.g. a globally-remembered 'plan' that a
workspace's OpenCode config no longer defines), but the create request
still submitted the raw stored modeId. Result: UI showed 'Build' while
the request sent 'plan', which the daemon then rejected.

Reconcile the selected mode against the discovered mode ids when
building the create config (both the workspace draft composer and the
workspace-setup dialog), so the submitted mode matches what the picker
shows — falling back to the first available mode when the stored one is
absent. Mirrors the existing resolveEffectiveModel reconciliation for
models.

* test(server): fix opencode mode tests after fallback change + rebase

- Replace 'available modes include build and plan' (which relied on the
  removed [build, plan] discovery-failure fallback) with two tests:
  one asserting discovered agents map to modes, one asserting empty
  discovery yields no fabricated modes.
- Restore messageId fields in the four provider-subagent timeline
  assertions. These were accidentally dropped during rebase conflict
  resolution; the #2013 subagent code legitimately emits messageId, so
  the expectations must include it.

* test(app): fix e2e seed helper using invalid opencode mode

createIdleAgent seeded an OpenCode agent with modeId
'bypassPermissions' — a Claude mode OpenCode never had. This worked
before only because the session create path silently coerced unknown
modes to 'build'. Now that create-time mode validation rejects modes
the provider doesn't define, use 'build' + auto_accept (OpenCode's
unattended full-access equivalent), matching the rewind-flow helper.

Fixes the e2e failures across archive-tab, command-center-host,
settings-toggle-tab-regression, workspace-agent-tab-rename,
workspace-pane-remount, workspace-navigation-regression, and
worktree-restore specs, which all seed via this helper.
2026-07-14 19:00:53 +08:00
Mohamed Boudra 28a27b02d3 Prevent Android chats from freezing or going blank (#1989)
* fix(app): stabilize streamed chat rendering

Use deterministic Markdown AST keys and preserve unique row identities when an assistant message resumes after a tool. Keep native gesture and plan-card children correctly keyed to avoid unsupported Fabric reconciliation paths.

* fix(app): stabilize retained native panels

Keep retained workspace roots in a stable native order and make active
panels present in the selection commit. Hidden panels now stop expensive
subscriptions and animations without losing mounted state.

* fix(app): harden retained panel activity
2026-07-11 15:39:14 +08:00
Mohamed Boudra 120f5d94a9 Fix compact web sheet swipe crashes 2026-05-18 15:29:01 +07:00
Mohamed Boudra d512932233 refactor: extract workspace terminal sessions, improve sidebar drag-and-drop with nestable lists, and consolidate tab presentation logic 2026-03-06 22:23:35 +07:00
Mohamed Boudra c233c99cd3 Update files 2026-01-26 18:03:51 +07:00
Mohamed Boudra 868f9fb571 WIP: Large refactor checkpoint
- Refactored daemon client and RPC layer
- Added new client package structure in server
- Removed legacy hooks (use-daemon-request, use-session-rpc, use-websocket)
- Added use-daemon-client hook
- Added e2e test infrastructure with Playwright
- Updated agent providers and session handling
- Removed codex-sdk patches
2026-01-12 13:30:20 +07:00
Mohamed BoudraandClaude f06170fcee feat: upgrade codex-sdk to 0.76.0 and claude-agent-sdk to 0.1.74
Upgrade packages to access latest model catalogs:
- @openai/codex-sdk: 0.58.0 → 0.76.0
- @anthropic-ai/claude-agent-sdk: 0.1.37 → 0.1.74

New models available after upgrade:
- Claude: Added Opus 4.5 and Haiku 4.5 to catalog
- Codex: Added gpt-5.2, gpt-5.2-codex, and gpt-5.1-codex variants

Updated patch file for new codex-sdk version.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-20 13:10:57 +07:00
Mohamed Boudra 95bacf8fc7 feat: add git branch/worktree setup, message queueing, file explorer improvements, and server restart 2025-11-22 23:20:42 +01:00