Skip to content

fixed lint errors#308

Merged
jmaxdev merged 32 commits into
mainfrom
dev
May 1, 2026
Merged

fixed lint errors#308
jmaxdev merged 32 commits into
mainfrom
dev

Conversation

@jmaxdev
Copy link
Copy Markdown
Collaborator

@jmaxdev jmaxdev commented May 1, 2026

No description provided.

jmaxdev and others added 28 commits April 29, 2026 01:59
* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…#293)

PR #284 shipped cloud chat as a single-shot non-streaming bridge —
every reply landed atomically once the request finished. That made the
cloud branch feel slower than Ollama, where the chat panel renders
tokens as they arrive. This wires SSE streaming through a new
`cloud_proxy_stream` Tauri command so the UX matches.

Rust
- New `cloud_proxy_stream` and `cloud_proxy_cancel` commands behind the
  existing `validate_cloud_proxy_request` policy. The command spawns a
  detached tokio task, registers a oneshot cancel handle keyed by the
  caller-supplied `streamId`, and emits `cloud-stream` events as
  `{ kind: "data" | "done" | "error", data?, error? }`.
- New `split_sse_events` parser splits the response buffer on
  `\n\n` / `\r\n\r\n`, joins multi-`data:` lines per event, and drops
  `event:` / `id:` / `retry:` / comment lines. The Rust side stays
  provider-agnostic; the per-provider JSON shape is parsed in TS.
- The `data: [DONE]` sentinel that OpenAI / OpenRouter use is collapsed
  to a structured `done` event so callers don't have to special-case it.
  Anthropic and Gemini close the connection silently on completion;
  the task synthesises a `done` event on EOF so the awaiter resolves
  uniformly.
- Total stream size capped by the same `CLOUD_PROXY_MAX_BODY` limit
  (16 MiB) that `cloud_proxy` uses, and per-request timeout uses
  `CLOUD_PROXY_TIMEOUT_SECS` (60 s). Cancellation tears the task down
  via the oneshot.
- 10 unit tests for `split_sse_events` covering single events, multi-line
  data joining, CRLF separators, partial-event buffering, the [DONE]
  sentinel, and the SSE-spec single-leading-space rule.

TypeScript
- New `streamCloudChat(req, onDelta)` mirroring `streamOllamaChat`'s
  shape: subscribes to `cloud-stream`, filters by `streamId`, parses
  the provider-specific delta, and resolves with the full text on done.
- Provider-specific delta extractors split into
  `extractStreamDelta(provider, raw)`:
  - OpenAI / OpenRouter: `choices[0].delta.content`
  - Anthropic: `content_block_delta` events with `delta.type === "text_delta"`
    only — `message_start`, `content_block_start`, `ping`, `message_stop`,
    `input_json_delta` (future tool deltas) are ignored
  - Gemini: `candidates[*].content.parts[*].text` joined
- The matching `extractFullResponse` is exported and unit-tested so the
  non-streaming `cloudChat` shares the same parsing surface.
- The provider URL / headers / body builder is consolidated into
  `buildProviderRequest(req, stream)`. Gemini's URL flips between
  `:generateContent` and `:streamGenerateContent?alt=sse`; the others
  just toggle `stream` in the body.
- 12 unit tests covering each provider's streaming and full-response
  branches, including the role-only chunk that OpenAI sends as the
  first delta and Anthropic's housekeeping events.

UI wiring
- `AiChatComponent` cloud branch now uses `streamCloudChat` with the
  same `placeholderPushed` / `appendToLastAiMessage` pattern the Ollama
  branch uses. If the stream fails before any content arrives we still
  render a clean error bubble; if it fails mid-stream we append the
  error inline so the user sees how far the model got.
- Tools / agent mode for cloud providers stays out of scope. Each
  provider has its own tool-call shape (`tool_calls` for OpenAI,
  `tool_use` blocks for Anthropic, `functionDeclarations` for Gemini)
  and that lands in the next PR.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 (was 113 — added 12 provider tests).
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 (was 102 — added 10 SSE-parser tests).
PR #284 shipped multi-provider keys behind a master switch and PR #286
added a banner warning that those keys lived in `settings.json`
plaintext. This wires the OS native secret store (review item I1) so
the keys never touch disk in clear text.

Backing store
- macOS: Keychain
- Windows: Credential Manager
- Linux: Secret Service / kwallet

The `keyring` crate (3.x) handles per-platform shimming. Pinned to 3.x
because keyring 4.x pulls `turso` (an embedded SQLite engine) and
`bon-macros` as transitive deps for its DB-backed unified store, none
of which we use; our `Entry::new` flow only needs the native OS
keychain backends that 3.x exposes by default.

Rust
- Four new commands behind a hard-coded provider allow-list
  (`openai` / `anthropic` / `gemini` / `openrouter`):
  `set_provider_secret`, `get_provider_secret`, `clear_provider_secret`,
  `has_provider_secret`. Service name is fixed at `trixty.ide` so
  entries are namespaced to the app and not visible to other tooling.
- `validate_secret_provider` rejects any string outside the allow-list
  so a renderer XSS can't probe arbitrary keychain entries.
- `keyring::Error::NoEntry` is mapped to `Ok(None)` / `Ok(false)` /
  `Ok(())` (idempotent clear), so the UI's "never configured" /
  "remove" paths don't surface false-positive error toasts.

TypeScript
- New `src/api/providerSecrets.ts` thin wrapper exposing
  `setProviderSecret` / `getProviderSecret` / `hasProviderSecret` /
  `clearProviderSecret` plus the `SECRET_PROVIDERS` list. Empty
  strings round-trip as "no key" — `setProviderSecret(p, "")` clears
  the entry, mirroring the way the old plaintext field treated `""`.
- Tauri-binding map extended with the four commands.

UI
- `ProviderKeysPanel` now loads each provider's key from the keychain
  on mount, holds it in component state for the reveal toggle and
  edit flow, and persists changes via a 500 ms debounced
  `setProviderSecret` (also flushed on blur). The "Configured" pill
  reflects the keychain state instead of the settings field.
- Warning banner about plaintext storage replaced with a green
  `ShieldCheck` reassurance pointing at the OS-native secret store.
- `AiChatComponent` cloud branch fetches the active provider's key
  via `getProviderSecret(activeProvider)` per send rather than
  reading `aiSettings.providerKeys`. Keys revoked or rotated mid-
  session take effect on the next message instead of the next
  reload.
- `keyForProvider` and the `ProviderKeys` import are removed from
  `client.ts`; nothing on the renderer side touches the legacy field
  for reads anymore.

Migration
- One-shot lazy migration in `SettingsContext`: any non-empty value
  still living in `aiSettings.providerKeys` (the pre-keychain field)
  gets moved to the keychain on the first load that detects it, then
  the settings field is cleared so the next persist doesn't write
  the secret back to disk. Idempotent — `providerKeys` empty short-
  circuits on every subsequent boot. Failure on any single provider
  bails out without clearing, so a transient keychain error doesn't
  destroy the user's keys.

Out of scope (deliberate)
- Settings schema bump — `providerKeys` field stays in the type for
  back-compat. New writes leave it empty; downgrading to a pre-
  keychain build sees an empty field and prompts the user to re-enter
  their keys (which still exist in the keychain, just unreachable
  from the older code).
- macOS Touch ID prompt suppression. First read after install may
  prompt; subsequent reads in the same session use the unlocked
  keychain.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 passing.
- pnpm build (Next 16 export) clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 passing.
- Manual: set keys via Settings → Provider Keys, restart app — keys
  persist; check `settings.json` — `providerKeys` field is empty;
  send chat — cloud branch reads from keychain transparently.
…emini / OpenRouter (#295)

PR #284 shipped multi-provider cloud chat as text-only. PR #293 added
streaming. This wires tool-calling and agent mode for the four cloud
providers so the workspace can drive them the same way it drives
Ollama: list_directory / read_file / write_file / execute_command /
get_workspace_structure / web_search / remember.

Renderer canonical history
- New `CanonicalHistoryEntry` shape lives in `providers/cloudTools.ts`
  alongside `ToolDefinition` and `UnifiedToolCall`. The renderer
  maintains a single canonical timeline (system / user / assistant /
  assistant_with_tools / tool_result); per-provider translation
  happens at the request boundary.
- `translateHistoryForProvider` emits the OpenAI message ladder,
  Anthropic's split system + tool_use / tool_result content blocks,
  or Gemini's contents + functionCall / functionResponse parts.
- `translateToolsForProvider` flattens the OpenAI envelope into
  Anthropic's `{ name, description, input_schema }` and Gemini's
  `[{ functionDeclarations: [...] }]`. OpenAI / OpenRouter pass
  through unchanged.
- `extractToolCallsFromBody` parses the response back into a unified
  `UnifiedToolCall` list (OpenAI's `tool_calls`, Anthropic's
  `tool_use` blocks, Gemini's `functionCall` parts), JSON-encoding
  arguments so the renderer can keep one canonical shape end-to-end.

Per-provider request
- New `cloudAgentChat(req)` in `providers/client.ts` — single-shot
  per turn. Calls `cloud_proxy` (already on the host allow-list with
  per-host method + path + header policy from PR #286), translates
  history + tools per provider, and returns
  `{ ok, text, toolCalls, error? }`.
- Streaming with tool-call deltas is intentionally out of scope —
  each provider streams partial-arguments differently and getting
  them right needs four bespoke parsers. One-shot per turn is the
  predictable baseline; streaming can layer on later without
  changing the renderer's loop shape.

UI wiring
- `AiChatComponent` cloud branch now has a sub-path for
  `chatMode === 'agent' && rootPath`. Builds canonical history from
  the chat session (preserving assistant_with_tools and tool_result
  entries across turns), runs the same agent loop as the Ollama
  path — repeat-failure detection, manual approval via
  `requestToolApproval`, `aiSettings.alwaysAllowTools`,
  `MAX_ITERATIONS = aiSettings.deepMode ? 15 : 5`, planner-mode
  gating left intact for the Ollama path. Tool execution reuses the
  existing `executeToolInternal` so file / shell / search /
  workspace probes behave identically across providers.
- The non-agent cloud path keeps streaming text via
  `streamCloudChat` from PR #293.

Tests
- 17 unit tests for `cloudTools.ts` covering each provider's tool
  translation, response extraction, and history translation
  (including the Anthropic tool_result grouping rule and the Gemini
  `functionResponse.response` object-wrapping fallback).

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 (was 125 — added 17 cloudTools tests).
- pnpm build (Next 16 export) clean.
- cargo build / clippy / fmt --check clean. cargo test --lib: 112 / 112.
PR #282 added detach for right-panel views. Bottom panel was the next
deferred item. Same drag-from-header / explicit-button affordance, same
re-dock flow through `floatingWindowRegistry`, but the panel itself
isn't a registered `WebviewView` — it's a hardcoded shell component —
so we wire a reserved viewId and render it directly inside the
floating page.

Registry
- `DetachablePanel` now accepts `"bottom"` alongside `"right"` / `"left"`.
- New `BOTTOM_PANEL_VIEW_ID` constant (`trixty.builtin.bottom-panel`)
  exported from the registry. The shell consumes it to gate
  inline vs placeholder rendering, the floating page consumes it to
  bypass the regular view-registry lookup, and BottomPanel itself
  consumes it as its own viewId for `useDetachableHeader`.

Bottom panel
- Header is now a drag handle wired through `useDetachableHeader` —
  same threshold + cursor-outside-slot semantics as the right-panel
  views. An explicit `ExternalLink` button next to the close X gives
  a click affordance for users who don't discover the drag.
- New `isFloating` prop (default `false`). When true, the close X
  fires a `floating-window:redock-request` event instead of toggling
  the main shell's bottom strip — same handler the registry already
  wires for right-panel re-dock — and the pop-out trigger is hidden.
- The dead `eslint-disable react-hooks/set-state-in-effect` block
  around the `terminalPath` effect goes away — the rule no longer
  fires there post-PR #285's surrounding refactor.

Floating page
- `viewId === BOTTOM_PANEL_VIEW_ID` short-circuits the regular
  `useRegisteredView` path and renders `<BottomPanel isFloating />`
  inside the same `FloatingTitleBar` shell the other views use.
  Title resolves from `panel.bottom.terminal_tabs`; icon stays
  consistent with the inline header.

Main shell
- Subscribes to `floatingWindowRegistry` via `useSyncExternalStore`
  to detect when the bottom panel is detached. The
  `<ResizablePanel id="bottom">` slot stays mounted (so the layout
  preset / resize history doesn't change shape mid-detach) and we
  swap its body to a `BottomPanelDetachedPlaceholder`. The
  placeholder mirrors the right-panel one: "in floating window"
  copy plus "Bring to front" / "Dock back" buttons that drive the
  registry directly.

Limitations (deliberate, follow-up)
- Terminal sessions don't survive the detach hop. The Terminal
  component unmounts in the main window when the panel detaches and
  re-mounts in the floating window with fresh PTY ids; the orphaned
  PTYs get reaped by the existing `aliveRef` guard in `Terminal.tsx`.
  Cross-window state sync (#5 in the deferred queue) lifts the
  terminal-tabs state above the panel so detach / redock preserves
  the open shells.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build (Next 16 export): /, /_not-found, /floating prerender clean.
- Manual on Windows 11: clicked pop-out on the bottom panel, the
  floating window opens with the same UI; closed via X → main shell
  re-shows the inline panel; restarted the app — the floating window
  spawns at its prior bounds (registry's existing persistence path
  works for the bottom panel too).
#297)

Each Tauri WebviewWindow runs its own JS realm, so detaching the AI
chat panel into a floating window today gave you two independent
\`ChatContext\` instances — sending a message in the float left the
main shell stuck on the prior conversation, and switching sessions
in either window had no effect on the other. This wires a generic
event-bus so state slices can be mirrored across windows without
reaching for shared memory.

Sync layer
- New \`src/api/crossWindowSync.ts\` exposes a tiny pub-sub layer over
  Tauri events. \`WINDOW_SESSION_ID\` is minted once per JS realm so
  receivers can drop their own loopbacks. \`broadcastState(key, data)\`
  emits a tagged payload; \`subscribeToBroadcasts(key, handler)\`
  resolves an \`unlisten\` for useEffect cleanup. Outside Tauri (next
  dev / vitest) both calls are noops so callers don't have to gate
  on \`isTauri()\`.

Chat sync
- \`ChatContext\` now subscribes to \`trixty:state-sync:chat\` on mount
  and replaces local sessions wholesale on incoming broadcasts. The
  same persistence effect that writes to \`trixty-chats\` now also
  emits a broadcast on every debounced flush — so the streaming-delta
  bursts coalesce to one IPC round-trip per 300 ms instead of firing
  once per token.
- \`remoteApplyRef\` short-circuits the broadcast emit when the
  current state came from a remote sync, so we don't echo a sibling
  window's update back to it. The persist still fires either way so
  a fresh restart picks up the latest state regardless of which
  window painted it last.

Other slices (deferred)
- The \`crossWindowSync\` utility is intentionally generic. Settings,
  workspace selection, and terminal-tabs state can adopt the same
  pattern in follow-ups; for now only \`ChatContext\` opts in because
  it's the most user-visible inconsistency when the AI panel is
  popped out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: detached the AI chat panel, sent a message in
  the float — the main shell's chat list updated within one debounce
  tick. Switched sessions in main → float caught up on next tick.
  Re-docked → no duplicate or stale messages remain.
PR #285 shipped Tree and Form visual surfaces for JSON / package.json
files. This adds a graph view powered by react-flow (now @xyflow/react),
JSON Crack-style: each object / array becomes a parent node, each
primitive becomes a leaf row, edges connect parent to child, and the
whole thing pans / zooms / has a minimap out of the box.

Visual surface
- New `JsonGraphEditor.tsx` walks the parsed JSON recursively to build
  react-flow nodes + edges. Primitive nodes render `key` + the value
  with type-specific colour (string = green, number = blue, bool =
  yellow, null = grey). Container nodes render `key` + a `[ N items ]`
  / `{ N keys }` summary. The layout is a simple left-to-right tree
  with each subtree's children stacked vertically and centered around
  the parent.
- 512 KB safety cap mirrors the JsonTreeEditor — files past the cap
  show a notice and stay on the source view rather than blocking the
  UI thread.
- Read-only by design. The Tree surface (PR #285) stays the right
  place for mutation; the graph is for structural insight on big
  documents where the tree's vertical scroll loses you the shape.

Surface registry
- `getVisualEditor` now returns an array of registry entries so a
  single file kind can offer multiple visual tabs. Generic `.json`
  registers Tree + Graph; `package.json` registers Form + Graph;
  `.env` keeps Table only. Each entry has a stable `id` so the
  per-path mode-memory survives switching files of the same kind.
- `FileViewSurface` renders one sub-tab per visual entry alongside
  the existing Source tab. Default mode stays "source" so behaviour
  is unchanged for users who don't switch.

Dependencies
- `@xyflow/react ^12.10.2` added to `apps/desktop/package.json`.
  Same package family that powers JSON Crack, n8n, Reactflow.dev's
  own demos. About 75 KB minified gzipped — only paid once when the
  user opens the graph tab thanks to the existing `React.lazy`
  registration in `getVisualEditor`.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean — the lazy
  import keeps `@xyflow/react` off the boot graph for users who never
  open a JSON file.
)

Issue #264 shipped tabular surfaces for .env and package.json. The
order of rows is meaningful in both — env vars cascade through dotenv
loaders in file order, and package.json scripts / deps are usually
ordered to make the file readable. Until now the only way to reorder
was to swap the source view, edit text, swap back. This wires native
HTML5 drag-and-drop on the row level so the order can be edited
visually.

Hook
- New `src/hooks/useDragReorder.ts` exposes a generic
  `useDragReorder({ items, getId, onReorder, groupKey? })` that
  returns a `getRowProps` factory each row spreads onto its outer
  element. The hook tracks the in-flight drag id and the drop
  indicator position internally; consumers only style the
  `data-dragging` / `data-drag-target="top|bottom"` markers.
- Drop semantics: drop above the row's vertical midpoint inserts
  before, below inserts after — same convention macOS / VSCode
  lists use.
- Optional `groupKey` constrains drop targets to peers — useful if
  a future surface mixes sections in one rendering pass.

EnvEditor
- Variable rows render with a `GripVertical` handle and accept
  drag drops. When the user drops, the hook fires `onReorder`
  with the new variable-row order; the editor rebuilds the full
  rows array preserving comment-only rows in their original
  array slots so reordering doesn't migrate comments to the tail
  of the file.

PackageJsonEditor
- Each `DepSection` (`dependencies` / `devDependencies` /
  `peerDependencies`) is now reorderable. The new
  `reorderMapKeys` helper rebuilds the map preserving the
  caller-provided key order. JS keeps insertion order for string
  keys, so `JSON.stringify` writes the new order to disk
  exactly. Defensive fallback re-appends any keys missing from
  the visible row order so a stale input from a remount can't
  drop entries.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: opened `apps/desktop/.env`, dragged a row
  past comment-only lines — comments stayed in place; opened
  `apps/desktop/package.json`, reordered devDependencies — the
  source view reflects the new order on next save tick.
Two repos side-by-side without context-switching. Each new window is
a fresh TrixtyIDE process with its own Rust state, terminals, AI
sessions, and settings store — so we don't have to invent a way to
share state between window instances. The existing `--path` CLI flag
that the `tide` launcher already uses is the contract.

Rust
- New `spawn_workspace_instance(path)` Tauri command resolves
  `current_exe()`, canonicalises the user-supplied folder, and
  spawns a detached `<binary> --path <folder>` process. Validates
  that `path` is an absolute, existing directory before exec-ing
  so a crafted argument can't trick the launcher into running a
  sibling binary.
- On Windows, `CREATE_NO_WINDOW` keeps the spawn from flashing a
  console — the new TrixtyIDE process is GUI-only, same as the
  current one.

UI
- New `Ctrl+Shift+N` shortcut in the main shell. Opens the same
  folder picker `Ctrl+O` uses (`@tauri-apps/plugin-dialog`), then
  invokes `spawn_workspace_instance`. Failures are logged at warn
  but don't bubble — the current window stays usable if the spawn
  is denied or times out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check / test --lib
  (112) clean.
- Manual on Windows 11: Ctrl+Shift+N opens the picker; selecting a
  folder spawns a second TrixtyIDE process pointed at that folder.
  Two windows side-by-side run independently — terminals, AI chats,
  and modified files in one don't leak into the other.
* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

* ci: add GitHub Actions workflow to automate Tauri build testing on Windows

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>
…loud streams (#301)

`OllamaStreams` and `CloudStreams` were both type aliases of
`Arc<Mutex<HashMap<String, oneshot::Sender<()>>>>`. Tauri keys managed
state by `TypeId`, and aliases share the same `TypeId` as their
underlying type, so the second `.manage()` call panicked at startup
with "state for type ... is already being managed".

Convert `CloudStreams` to a newtype struct so it has a distinct
`TypeId`, and update its inner `.lock()` call sites accordingly.
…ID (#302)

The runtime fallback used Math.random, which CodeQL flags as
js/insecure-randomness (CWE-338). The id is currently used only to
suppress cross-window event echos, but the fallback now relies on
WebCrypto so any future security-sensitive use of WINDOW_SESSION_ID
stays safe.

Order of preference:
  1. crypto.randomUUID         — modern Tauri webview, Node 19+, browsers
  2. crypto.getRandomValues    — older jsdom / runtimes without randomUUID
  3. Date.now + performance.now — last-resort, only echo-suppression
…figuration settings for the Trixty IDE desktop application.
…synchronization, and Discord RPC integration
…status, file metadata, and keyboard-navigable tab management
@jmaxdev jmaxdev enabled auto-merge (squash) May 1, 2026 21:40
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 1, 2026

Hi @jmaxdev, the quality checks have failed.

❌ Quality Checks Failed

Check Status
Dependencies ✅ Success
Sandbox Worker Bundle ✅ Success
Lint ✅ Success
Typecheck ❌ Failure
Clippy ⚪ Skipped
Format ⚪ Skipped
Vitest ⚪ Skipped
Cargo Test ⚪ Skipped
Full Log (Typecheck)
src/lib/awareness.test.ts(77,40): error TS2345: Argument of type '{ system: { os_name: string; os_version: string; arch: string; app_version: string; cpu_usage: number; memory_usage: number; }; stack: { packageManager: "pnpm"; frameworks: string[]; isTypeScript: boolean; ... 4 more ...; languages: never[]; }; ... 6 more ...; internetAccess: string; }' is not assignable to parameter of type '{ system: SystemInfo; stack: ProjectStack; settings: { ai: AISettings; editor: EditorSettings; system: SystemSettings; locale: string; }; ... 5 more ...; internetAccess?: string | undefined; }'.
  The types of 'settings.system' are incompatible between these types.
    Property 'discord' is missing in type '{ hasCompletedOnboarding: boolean; filesExclude: string[]; updateChannel: "stable"; }' but required in type 'SystemSettings'.
src/lib/awareness.test.ts(86,40): error TS2345: Argument of type '{ system: { os_name: string; os_version: string; arch: string; app_version: string; cpu_usage: number; memory_usage: number; }; stack: { packageManager: "pnpm"; frameworks: string[]; isTypeScript: boolean; ... 4 more ...; languages: never[]; }; ... 6 more ...; internetAccess: string; }' is not assignable to parameter of type '{ system: SystemInfo; stack: ProjectStack; settings: { ai: AISettings; editor: EditorSettings; system: SystemSettings; locale: string; }; ... 5 more ...; internetAccess?: string | undefined; }'.
  The types of 'settings.system' are incompatible between these types.
    Property 'discord' is missing in type '{ hasCompletedOnboarding: boolean; filesExclude: string[]; updateChannel: "stable"; }' but required in type 'SystemSettings'.
src/lib/awareness.test.ts(93,40): error TS2345: Argument of type '{ mode: string; system: { os_name: string; os_version: string; arch: string; app_version: string; cpu_usage: number; memory_usage: number; }; stack: { packageManager: "pnpm"; frameworks: string[]; isTypeScript: boolean; ... 4 more ...; languages: never[]; }; ... 5 more ...; internetAccess: string; }' is not assignable to parameter of type '{ system: SystemInfo; stack: ProjectStack; settings: { ai: AISettings; editor: EditorSettings; system: SystemSettings; locale: string; }; ... 5 more ...; internetAccess?: string | undefined; }'.
  The types of 'settings.system' are incompatible between these types.
    Property 'discord' is missing in type '{ hasCompletedOnboarding: boolean; filesExclude: string[]; updateChannel: "stable"; }' but required in type 'SystemSettings'.
src/lib/awareness.test.ts(98,40): error TS2345: Argument of type '{ skills: never[]; docs: never[]; system: { os_name: string; os_version: string; arch: string; app_version: string; cpu_usage: number; memory_usage: number; }; stack: { packageManager: "pnpm"; frameworks: string[]; ... 5 more ...; languages: never[]; }; ... 4 more ...; internetAccess: string; }' is not assignable to parameter of type '{ system: SystemInfo; stack: ProjectStack; settings: { ai: AISettings; editor: EditorSettings; system: SystemSettings; locale: string; }; ... 5 more ...; internetAccess?: string | undefined; }'.
  The types of 'settings.system' are incompatible between these types.
    Property 'discord' is missing in type '{ hasCompletedOnboarding: boolean; filesExclude: string[]; updateChannel: "stable"; }' but required in type 'SystemSettings'.
src/lib/awareness.test.ts(109,40): error TS2345: Argument of type '{ projectTreeSummary: string[]; system: { os_name: string; os_version: string; arch: string; app_version: string; cpu_usage: number; memory_usage: number; }; stack: { packageManager: "pnpm"; frameworks: string[]; ... 5 more ...; languages: never[]; }; ... 5 more ...; internetAccess: string; }' is not assignable to parameter of type '{ system: SystemInfo; stack: ProjectStack; settings: { ai: AISettings; editor: EditorSettings; system: SystemSettings; locale: string; }; ... 5 more ...; internetAccess?: string | undefined; }'.
  The types of 'settings.system' are incompatible between these types.
    Property 'discord' is missing in type '{ hasCompletedOnboarding: boolean; filesExclude: string[]; updateChannel: "stable"; }' but required in type 'SystemSettings'.
src/lib/awareness.test.ts(120,40): error TS2345: Argument of type '{ rootPath: null; system: { os_name: string; os_version: string; arch: string; app_version: string; cpu_usage: number; memory_usage: number; }; stack: { packageManager: "pnpm"; frameworks: string[]; ... 5 more ...; languages: never[]; }; ... 5 more ...; internetAccess: string; }' is not assignable to parameter of type '{ system: SystemInfo; stack: ProjectStack; settings: { ai: AISettings; editor: EditorSettings; system: SystemSettings; locale: string; }; ... 5 more ...; internetAccess?: string | undefined; }'.
  The types of 'settings.system' are incompatible between these types.
    Property 'discord' is missing in type '{ hasCompletedOnboarding: boolean; filesExclude: string[]; updateChannel: "stable"; }' but required in type 'SystemSettings'.
src/lib/awareness.test.ts(125,40): error TS2345: Argument of type '{ rootPath: null; mode: string; system: { os_name: string; os_version: string; arch: string; app_version: string; cpu_usage: number; memory_usage: number; }; stack: { packageManager: "pnpm"; frameworks: string[]; ... 5 more ...; languages: never[]; }; ... 4 more ...; internetAccess: string; }' is not assignable to parameter of type '{ system: SystemInfo; stack: ProjectStack; settings: { ai: AISettings; editor: EditorSettings; system: SystemSettings; locale: string; }; ... 5 more ...; internetAccess?: string | undefined; }'.
  The types of 'settings.system' are incompatible between these types.
    Property 'discord' is missing in type '{ hasCompletedOnboarding: boolean; filesExclude: string[]; updateChannel: "stable"; }' but required in type 'SystemSettings'.
src/lib/awareness.test.ts(136,40): error TS2345: Argument of type '{ system: { os_name: string; os_version: string; arch: string; app_version: string; cpu_usage: number; memory_usage: number; }; stack: { packageManager: "pnpm"; frameworks: string[]; isTypeScript: boolean; ... 4 more ...; languages: never[]; }; ... 6 more ...; internetAccess: string; }' is not assignable to parameter of type '{ system: SystemInfo; stack: ProjectStack; settings: { ai: AISettings; editor: EditorSettings; system: SystemSettings; locale: string; }; ... 5 more ...; internetAccess?: string | undefined; }'.
  The types of 'settings.system' are incompatible between these types.
    Property 'discord' is missing in type '{ hasCompletedOnboarding: boolean; filesExclude: string[]; updateChannel: "stable"; }' but required in type 'SystemSettings'.
src/lib/awareness.test.ts(141,40): error TS2345: Argument of type '{ stack: { languages: string[]; testRunners: string[]; linters: string[]; formatters: string[]; buildTools: string[]; packageManager: "pnpm"; frameworks: string[]; isTypeScript: boolean; }; system: { os_name: string; ... 4 more ...; memory_usage: number; }; ... 6 more ...; internetAccess: string; }' is not assignable to parameter of type '{ system: SystemInfo; stack: ProjectStack; settings: { ai: AISettings; editor: EditorSettings; system: SystemSettings; locale: string; }; ... 5 more ...; internetAccess?: string | undefined; }'.
  The types of 'settings.system' are incompatible between these types.
    Property 'discord' is missing in type '{ hasCompletedOnboarding: boolean; filesExclude: string[]; updateChannel: "stable"; }' but required in type 'SystemSettings'.
src/lib/awareness.test.ts(160,40): error TS2345: Argument of type '{ system: { os_name: string; os_version: string; arch: string; app_version: string; cpu_usage: number; memory_usage: number; }; stack: { packageManager: "pnpm"; frameworks: string[]; isTypeScript: boolean; ... 4 more ...; languages: never[]; }; ... 6 more ...; internetAccess: string; }' is not assignable to parameter of type '{ system: SystemInfo; stack: ProjectStack; settings: { ai: AISettings; editor: EditorSettings; system: SystemSettings; locale: string; }; ... 5 more ...; internetAccess?: string | undefined; }'.
  The types of 'settings.system' are incompatible between these types.
    Property 'discord' is missing in type '{ hasCompletedOnboarding: boolean; filesExclude: string[]; updateChannel: "stable"; }' but required in type 'SystemSettings'.
undefined
D:\a\ide\ide\apps\desktop:
 ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL  Command "tsc" not found

View full logs

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 1, 2026

Hi @jmaxdev, the quality checks have failed.

❌ Quality Checks Failed

Check Status
Dependencies ✅ Success
Sandbox Worker Bundle ✅ Success
Lint ✅ Success
Typecheck ✅ Success
Clippy ❌ Failure
Format ⚪ Skipped
Vitest ⚪ Skipped
Cargo Test ⚪ Skipped
Full Log (Clippy)
    Updating crates.io index
 Downloading crates ...
  Downloaded borsh-derive v1.6.1
  Downloaded phf_generator v0.10.0
  Downloaded phf_shared v0.8.0
  Downloaded ref-cast v1.0.25
  Downloaded serde-untagged v0.1.9
  Downloaded serde_core v1.0.228
  Downloaded thiserror-impl v1.0.69
  Downloaded windows-interface v0.59.3
  Downloaded windows-implement v0.60.2
  Downloaded windows-core v0.61.2
  Downloaded zerovec-derive v0.11.3
  Downloaded zmij v1.0.21
  Downloaded windows-targets v0.53.5
  Downloaded windows-result v0.4.1
  Downloaded typenum v1.19.0
  Downloaded windows-threading v0.1.0
  Downloaded tracing-subscriber v0.3.23
  Downloaded windows-registry v0.6.1
  Downloaded tracing v0.1.44
  Downloaded toml_edit v0.25.11+spec-1.1.0
  Downloaded webpki-roots v1.0.7
  Downloaded url v2.5.8
  Downloaded unicode-width v0.1.13
  Downloaded tokio v1.52.0
  Downloaded unicode-width v0.2.2
  Downloaded unicode-segmentation v1.13.2
  Downloaded zerotrie v0.2.4
  Downloaded zip v4.6.1
  Downloaded zerovec v0.11.6
  Downloaded time v0.3.47
  Downloaded tower v0.5.3
  Downloaded winnow v1.0.1
  Downloaded winnow v0.7.15
  Downloaded tokio-util v0.7.18
  Downloaded windows-link v0.2.1
  Downloaded ureq v2.12.1
  Downloaded zerocopy v0.8.48
  Downloaded tower-http v0.6.8
  Downloaded windows-targets v0.52.6
  Downloaded windows-targets v0.48.5
  Downloaded windows-strings v0.4.2
  Downloaded windows-numerics v0.2.0
  Downloaded windows-link v0.1.3
  Downloaded serde_with v3.11.0
  Downloaded ring v0.17.14
  Downloaded winapi v0.3.9
  Downloaded zerofrom v0.1.7
  Downloaded yoke v0.8.2
  Downloaded writeable v0.6.3
  Downloaded windows_x86_64_msvc v0.48.5
  Downloaded windows-version v0.1.7
  Downloaded windows_x86_64_msvc v0.52.6
  Downloaded windows-strings v0.5.1
  Downloaded windows-result v0.3.4
  Downloaded zeroize v1.8.2
  Downloaded window-vibrancy v0.6.0
  Downloaded zerofrom-derive v0.1.7
  Downloaded yoke-derive v0.8.2
  Downloaded wyz v0.5.1
  Downloaded winreg v0.55.0
  Downloaded winreg v0.10.1
  Downloaded tauri-plugin-window-state v2.4.1
  Downloaded tauri-plugin-updater v2.10.1
  Downloaded tauri-plugin-store v2.4.2
  Downloaded tauri-plugin-positioner v2.3.1
  Downloaded tauri-plugin-http v2.5.8
  Downloaded tauri-plugin-dialog v2.7.0
  Downloaded tao v0.34.8
  Downloaded sysinfo v0.36.1
  Downloaded syn v1.0.109
  Downloaded serde_json v1.0.149
  Downloaded rustls v0.23.38
  Downloaded winapi-util v0.1.11
  Downloaded tauri-runtime-wry v2.10.1
  Downloaded rust_decimal v1.41.0
  Downloaded windows-collections v0.2.0
  Downloaded webview2-com-macros v0.8.1
  Downloaded webview2-com v0.38.2
  Downloaded want v0.3.1
  Downloaded walkdir v2.5.0
  Downloaded vswhom-sys v0.1.3
  Downloaded vswhom v0.1.0
  Downloaded version_check v0.9.5
  Downloaded value-bag v1.12.0
  Downloaded uuid v1.23.0
  Downloaded utf8_iter v1.0.4
  Downloaded utf8-width v0.1.8
  Downloaded utf-8 v0.7.6
  Downloaded urlpattern v0.3.0
  Downloaded urlencoding v2.1.3
  Downloaded untrusted v0.9.0
  Downloaded windows_x86_64_msvc v0.53.1
  Downloaded unicode-ident v1.0.24
  Downloaded unic-ucd-version v0.9.0
  Downloaded unic-ucd-ident v0.9.0
  Downloaded tray-icon v0.21.3
  Downloaded tracing-core v0.1.36
  Downloaded tracing-attributes v0.1.31
  Downloaded toml_writer v1.1.1+spec-1.1.0
  Downloaded toml_datetime v1.1.1+spec-1.1.0
  Downloaded tokio-native-tls v0.3.1
  Downloaded tinyvec v1.11.0
  Downloaded tinystr v0.8.3
  Downloaded tauri-utils v2.8.3
  Downloaded tauri-plugin-shell v2.3.5
  Downloaded tauri-plugin-process v2.3.1
  Downloaded tauri-plugin-log v2.8.0
  Downloaded tauri v2.10.3
  Downloaded syn v2.0.117
  Downloaded schemars v1.2.1
  Downloaded rustls-webpki v0.103.13
  Downloaded wry v0.54.4
  Downloaded windows-future v0.2.1
  Downloaded unic-common v0.9.0
  Downloaded unic-char-range v0.9.0
  Downloaded unic-char-property v0.9.0
  Downloaded typeid v1.0.3
  Downloaded try-lock v0.2.5
  Downloaded tracing-log v0.2.0
  Downloaded tower-service v0.3.3
  Downloaded tower-layer v0.3.3
  Downloaded toml_parser v1.1.2+spec-1.1.0
  Downloaded toml_datetime v0.7.5+spec-1.1.0
  Downloaded toml v0.9.12+spec-1.1.0
  Downloaded tokio-rustls v0.26.4
  Downloaded tokio-macros v2.7.0
  Downloaded tinyvec_macros v0.1.1
  Downloaded time-macros v0.2.27
  Downloaded time-core v0.1.8
  Downloaded thread_local v1.1.9
  Downloaded thiserror-impl v2.0.18
  Downloaded windows-sys v0.59.0
  Downloaded thiserror v1.0.69
  Downloaded tauri-runtime v2.10.1
  Downloaded tauri-plugin-fs v2.5.0
  Downloaded tauri-codegen v2.5.5
  Downloaded sync_wrapper v1.0.2
  Downloaded strsim v0.10.0
  Downloaded serde v1.0.228
  Downloaded reqwest v0.13.2
  Downloaded reqwest v0.12.28
  Downloaded regex-syntax v0.8.10
  Downloaded regex-automata v0.4.14
  Downloaded rand v0.8.6
  Downloaded quinn-proto v0.11.14
  Downloaded windows-sys v0.48.0
  Downloaded thiserror v2.0.18
  Downloaded tendril v0.4.3
  Downloaded tempfile v3.27.0
  Downloaded tauri-winres v0.3.5
  Downloaded tauri-plugin v2.5.4
  Downloaded tauri-macros v2.5.5
  Downloaded tauri-build v2.5.6
  Downloaded tap v1.0.1
  Downloaded synstructure v0.13.2
  Downloaded subtle v2.6.1
  Downloaded string_cache_codegen v0.5.4
  Downloaded string_cache v0.8.9
  Downloaded stable_deref_trait v1.2.1
  Downloaded softbuffer v0.4.8
  Downloaded socket2 v0.6.3
  Downloaded smallvec v1.15.1
  Downloaded slab v0.4.12
  Downloaded siphasher v1.0.2
  Downloaded siphasher v0.3.11
  Downloaded simdutf8 v0.1.5
  Downloaded simd-adler32 v0.3.9
  Downloaded shlex v1.3.0
  Downloaded shell-words v1.1.1
  Downloaded shared_library v0.1.9
  Downloaded shared_child v1.1.1
  Downloaded sharded-slab v0.1.7
  Downloaded sha2 v0.10.9
  Downloaded servo_arc v0.3.0
  Downloaded servo_arc v0.2.0
  Downloaded windows-sys v0.61.2
  Downloaded serialize-to-javascript-impl v0.1.2
  Downloaded serialize-to-javascript v0.1.2
  Downloaded serial2 v0.2.36
  Downloaded windows-sys v0.60.2
  Downloaded serde_with_macros v3.11.0
  Downloaded serde_urlencoded v0.7.1
  Downloaded serde_spanned v1.1.1
  Downloaded serde_repr v0.1.20
  Downloaded serde_derive_internals v0.29.1
  Downloaded serde_derive v1.0.228
  Downloaded sentry-contexts v0.34.0
  Downloaded seahash v4.1.0
  Downloaded scraper v0.19.1
  Downloaded schemars_derive v0.8.22
  Downloaded ryu v1.0.23
  Downloaded rustls-pki-types v1.14.0
  Downloaded rustls-native-certs v0.8.3
  Downloaded rustc_version v0.4.1
  Downloaded rustc-demangle v0.1.27
  Downloaded rkyv v0.7.46
  Downloaded regex v1.12.3
  Downloaded ref-cast-impl v1.0.25
  Downloaded rand_chacha v0.9.0
  Downloaded rand_chacha v0.2.2
  Downloaded rand v0.9.4
  Downloaded quinn v0.11.9
  Downloaded publicsuffix v2.3.0
  Downloaded proc-macro-crate v3.5.0
  Downloaded portable-pty v0.9.0
  Downloaded png v0.17.16
  Downloaded phf_shared v0.11.3
  Downloaded fxhash v0.2.1
  Downloaded futf v0.1.5
  Downloaded filetime v0.2.27
  Downloaded fdeflate v0.3.7
  Downloaded fastrand v2.4.1
  Downloaded digest v0.10.7
  Downloaded darling_core v0.20.6
  Downloaded darling v0.20.6
  Downloaded cc v1.2.60
  Downloaded bytes v1.11.1
  Downloaded bytecheck_derive v0.6.12
  Downloaded brotli-decompressor v5.0.0
  Downloaded borsh v1.6.1
  Downloaded backtrace v0.3.76
  Downloaded aho-corasick v1.1.4
  Downloaded sentry-types v0.34.0
  Downloaded sentry-tracing v0.34.0
  Downloaded sentry-panic v0.34.0
  Downloaded sentry-debug-images v0.34.0
  Downloaded sentry-core v0.34.0
  Downloaded sentry-backtrace v0.34.0
  Downloaded sentry v0.34.0
  Downloaded semver v1.0.28
  Downloaded selectors v0.25.0
  Downloaded selectors v0.24.0
  Downloaded scopeguard v1.2.0
  Downloaded schemars v0.8.22
  Downloaded schannel v0.1.29
  Downloaded same-file v1.0.6
  Downloaded rustls-platform-verifier v0.6.2
  Downloaded rustc-hash v2.1.2
  Downloaded rkyv_derive v0.7.46
  Downloaded rfd v0.16.0
  Downloaded rend v0.4.2
  Downloaded raw-window-handle v0.6.2
  Downloaded quote v1.0.45
  Downloaded psl-types v2.0.11
  Downloaded ntapi v0.4.3
  Downloaded nix v0.28.0
  Downloaded mio v1.2.0
  Downloaded libc v0.2.185
  Downloaded indexmap v2.14.0
  Downloaded icu_properties_data v2.1.2
  Downloaded hyper v1.9.0
  Downloaded hashbrown v0.17.0
  Downloaded h2 v0.4.13
  Downloaded futures-util v0.3.32
  Downloaded futures-sink v0.3.32
  Downloaded futures-channel v0.3.32
  Downloaded findshlibs v0.10.2
  Downloaded fern v0.7.1
  Downloaded erased-serde v0.4.10
  Downloaded ego-tree v0.6.3
  Downloaded dpi v0.1.2
  Downloaded downcast-rs v1.2.1
  Downloaded dirs v6.0.0
  Downloaded debugid v0.8.0
  Downloaded crossbeam-utils v0.8.21
  Downloaded crossbeam-epoch v0.9.18
  Downloaded crossbeam-deque v0.8.6
  Downloaded cookie v0.18.1
  Downloaded chrono v0.4.44
  Downloaded cargo_toml v0.22.3
  Downloaded cargo_metadata v0.19.2
  Downloaded bytecheck v0.6.12
  Downloaded async-compression v0.4.41
  Downloaded arrayvec v0.7.6
  Downloaded alloc-no-stdlib v2.0.4
  Downloaded rand_pcg v0.2.1
  Downloaded rand_core v0.9.5
  Downloaded rand_core v0.6.4
  Downloaded rand_core v0.5.1
  Downloaded rand_chacha v0.3.1
  Downloaded webview2-com-sys v0.38.2
  Downloaded rand v0.7.3
  Downloaded radium v0.7.0
  Downloaded powerfmt v0.2.0
  Downloaded parking_lot v0.12.5
  Downloaded num-traits v0.2.19
  Downloaded muda v0.17.2
  Downloaded miniz_oxide v0.8.9
  Downloaded memmap2 v0.9.10
  Downloaded memchr v2.8.0
  Downloaded log v0.4.29
  Downloaded litrs v1.0.0
  Downloaded litemap v0.8.2
  Downloaded kuchikiki v0.8.8-speedreader
  Downloaded keyring v3.6.3
  Downloaded keyboard-types v0.7.0
  Downloaded jsonptr v0.6.3
  Downloaded iri-string v0.7.12
  Downloaded indexmap v1.9.3
  Downloaded ignore v0.4.25
  Downloaded idna v1.1.0
  Downloaded icu_provider v2.1.1
  Downloaded icu_properties v2.1.2
  Downloaded icu_normalizer_data v2.1.1
  Downloaded icu_normalizer v2.1.0
  Downloaded icu_locale_core v2.1.1
  Downloaded icu_collections v2.1.1
  Downloaded hyper-util v0.1.20
  Downloaded httparse v1.10.1
  Downloaded http v1.4.0
  Downloaded html5ever v0.29.1
  Downloaded html5ever v0.27.0
  Downloaded html2text v0.12.6
  Downloaded hashbrown v0.12.3
  Downloaded grep-regex v0.1.14
  Downloaded glob v0.3.3
  Downloaded getrandom v0.4.2
  Downloaded getrandom v0.2.17
  Downloaded generic-array v0.14.7
  Downloaded futures-io v0.3.32
  Downloaded form_urlencoded v1.2.2
  Downloaded fnv v1.0.7
  Downloaded filedescriptor v0.8.3
  Downloaded encoding_rs_io v0.1.7
  Downloaded embed-resource v3.0.8
  Downloaded dtoa-short v0.3.5
  Downloaded dtoa v1.0.11
  Downloaded document-features v0.2.12
  Downloaded dirs-sys v0.4.1
  Downloaded dirs v5.0.1
  Downloaded derive_more v0.99.20
  Downloaded ctor v0.2.9
  Downloaded crypto-common v0.1.7
  Downloaded crossbeam-channel v0.5.15
  Downloaded convert_case v0.4.0
  Downloaded compression-core v0.4.31
  Downloaded cfg_aliases v0.1.1
  Downloaded cfb v0.7.3
  Downloaded cargo-platform v0.1.9
  Downloaded byteorder v1.5.0
  Downloaded block-buffer v0.10.4
  Downloaded bitflags v1.3.2
  Downloaded base64 v0.22.1
  Downloaded anyhow v1.0.102
  Downloaded ahash v0.8.12
  Downloaded ahash v0.7.8
  Downloaded quinn-udp v0.5.14
  Downloaded ptr_meta_derive v0.1.4
  Downloaded ptr_meta v0.1.4
  Downloaded proc-macro2 v1.0.106
  Downloaded proc-macro-hack v0.5.20+deprecated
  Downloaded precomputed-hash v0.1.1
  Downloaded ppv-lite86 v0.2.21
  Downloaded potential_utf v0.1.5
  Downloaded pin-project-lite v0.2.17
  Downloaded phf_shared v0.10.0
  Downloaded phf_macros v0.11.3
  Downloaded parking_lot_core v0.9.12
  Downloaded os_info v3.14.0
  Downloaded once_cell v1.21.4
  Downloaded nu-ansi-term v0.50.3
  Downloaded notify v6.1.1
  Downloaded native-tls v0.2.18
  Downloaded minisign-verify v0.2.5
  Downloaded markup5ever v0.14.1
  Downloaded markup5ever v0.12.1
  Downloaded mac v0.1.1
  Downloaded lru-slab v0.1.2
  Downloaded itoa v1.0.18
  Downloaded ipnet v2.12.0
  Downloaded infer v0.19.0
  Downloaded idna_adapter v1.2.1
  Downloaded ident_case v1.0.1
  Downloaded ico v0.5.0
  Downloaded http-body-util v0.1.3
  Downloaded http-body v1.0.1
  Downloaded hostname v0.4.2
  Downloaded hex v0.4.3
  Downloaded grep-searcher v0.1.16
  Downloaded globset v0.4.18
  Downloaded encoding_rs v0.8.35
  Downloaded crc32fast v1.5.0
  Downloaded cfg_aliases v0.2.1
  Downloaded bstr v1.12.1
  Downloaded brotli v8.0.2
  Downloaded phf_macros v0.10.0
  Downloaded phf_generator v0.11.3
  Downloaded phf_generator v0.8.0
  Downloaded phf_codegen v0.11.3
  Downloaded phf_codegen v0.10.0
  Downloaded phf_codegen v0.8.0
  Downloaded phf v0.11.3
  Downloaded phf v0.10.1
  Downloaded phf v0.8.0
  Downloaded percent-encoding v2.3.2
  Downloaded os_pipe v1.2.3
  Downloaded option-ext v0.2.0
  Downloaded open v5.3.3
  Downloaded num-conv v0.2.1
  Downloaded nodrop v0.1.14
  Downloaded mime v0.3.17
  Downloaded matches v0.1.10
  Downloaded matchers v0.2.0
  Downloaded lazy_static v1.5.0
  Downloaded json-patch v3.0.1
  Downloaded hyper-tls v0.6.0
  Downloaded hyper-rustls v0.27.9
  Downloaded httpdate v1.0.3
  Downloaded grep-matcher v0.1.8
  Downloaded getrandom v0.3.4
  Downloaded getrandom v0.1.16
  Downloaded getopts v0.2.24
  Downloaded futures-task v0.3.32
  Downloaded futures-macro v0.3.32
  Downloaded futures-core v0.3.32
  Downloaded funty v2.0.0
  Downloaded flate2 v1.1.9
  Downloaded find-msvc-tools v0.1.9
  Downloaded equivalent v1.0.2
  Downloaded dunce v1.0.5
  Downloaded dirs-sys v0.5.0
  Downloaded data-url v0.3.2
  Downloaded cssparser-macros v0.6.1
  Downloaded cssparser v0.31.2
  Downloaded cfg-if v1.0.4
  Downloaded byte-unit v5.2.0
  Downloaded bitvec v1.0.1
  Downloaded autocfg v1.5.0
  Downloaded atomic-waker v1.1.2
  Downloaded adler2 v2.0.1
  Downloaded new_debug_unreachable v1.0.6
  Downloaded match_token v0.1.0
  Downloaded lock_api v0.4.14
  Downloaded heck v0.5.0
  Downloaded dyn-clone v1.0.20
  Downloaded displaydoc v0.2.5
  Downloaded deranged v0.5.8
  Downloaded darling_macro v0.20.6
  Downloaded cssparser v0.29.6
  Downloaded cpufeatures v0.2.17
  Downloaded cookie_store v0.22.0
  Downloaded compression-codecs v0.4.37
  Downloaded camino v1.2.2
  Downloaded bitflags v2.11.1
  Downloaded alloc-stdlib v0.2.2
  Downloaded windows v0.61.3
   Compiling proc-macro2 v1.0.106
   Compiling unicode-ident v1.0.24
   Compiling quote v1.0.45
   Compiling cfg-if v1.0.4
   Compiling zerocopy v0.8.48
   Compiling serde_core v1.0.228
   Compiling getrandom v0.2.17
   Compiling rand_core v0.6.4
   Compiling icu_properties_data v2.1.2
   Compiling icu_normalizer_data v2.1.1
   Compiling syn v2.0.117
    Checking windows-link v0.2.1
   Compiling zmij v1.0.21
   Compiling siphasher v1.0.2
   Compiling phf_shared v0.11.3
   Compiling getrandom v0.4.2
   Compiling thiserror v1.0.69
   Compiling siphasher v0.3.11
   Compiling stable_deref_trait v1.2.1
   Compiling serde v1.0.228
   Compiling parking_lot_core v0.9.12
   Compiling smallvec v1.15.1
   Compiling thiserror v2.0.18
   Compiling ppv-lite86 v0.2.21
   Compiling getrandom v0.1.16
   Compiling rand_chacha v0.3.1
   Compiling phf_shared v0.10.0
   Compiling synstructure v0.13.2
   Compiling rand v0.8.6
   Compiling fnv v1.0.7
    Checking memchr v2.8.0
    Checking windows-sys v0.61.2
   Compiling phf_generator v0.11.3
   Compiling phf_generator v0.10.0
   Compiling phf_codegen v0.11.3
   Compiling string_cache_codegen v0.5.4
   Compiling typeid v1.0.3
   Compiling rand_core v0.5.1
   Compiling syn v1.0.109
   Compiling strsim v0.10.0
   Compiling writeable v0.6.3
   Compiling itoa v1.0.18
   Compiling shlex v1.3.0
   Compiling litemap v0.8.2
   Compiling find-msvc-tools v0.1.9
   Compiling serde_derive v1.0.228
   Compiling zerofrom-derive v0.1.7
   Compiling yoke-derive v0.8.2
   Compiling zerovec-derive v0.11.3
   Compiling displaydoc v0.2.5
   Compiling thiserror-impl v1.0.69
   Compiling zerofrom v0.1.7
   Compiling yoke v0.8.2
   Compiling phf_macros v0.11.3
   Compiling zerovec v0.11.6
   Compiling thiserror-impl v2.0.18
   Compiling ident_case v1.0.1
   Compiling autocfg v1.5.0
   Compiling tinystr v0.8.3
   Compiling icu_locale_core v2.1.1
   Compiling potential_utf v0.1.5
   Compiling darling_core v0.20.6
   Compiling zerotrie v0.2.4
   Compiling cc v1.2.60
   Compiling semver v1.0.28
   Compiling erased-serde v0.4.10
   Compiling icu_provider v2.1.1
   Compiling icu_collections v2.1.1
   Compiling darling_macro v0.20.6
   Compiling rand_chacha v0.2.2
   Compiling rand_pcg v0.2.1
   Compiling phf_shared v0.8.0
   Compiling proc-macro-hack v0.5.20+deprecated
   Compiling anyhow v1.0.102
   Compiling rand v0.7.3
   Compiling darling v0.20.6
   Compiling cssparser-macros v0.6.1
   Compiling serde_json v1.0.149
   Compiling scopeguard v1.2.0
   Compiling new_debug_unreachable v1.0.6
   Compiling byteorder v1.5.0
   Compiling phf_generator v0.8.0
   Compiling lock_api v0.4.14
   Compiling icu_normalizer v2.1.0
   Compiling icu_properties v2.1.2
   Compiling serde_with_macros v3.11.0
   Compiling winnow v1.0.1
   Compiling convert_case v0.4.0
   Compiling mac v0.1.1
   Compiling precomputed-hash v0.1.1
   Compiling futf v0.1.5
   Compiling derive_more v0.99.20
   Compiling toml_parser v1.1.2+spec-1.1.0
   Compiling phf_macros v0.10.0
   Compiling cssparser v0.29.6
   Compiling parking_lot v0.12.5
   Compiling idna_adapter v1.2.1
   Compiling phf_codegen v0.8.0
   Compiling markup5ever v0.14.1
   Compiling toml_datetime v0.7.5+spec-1.1.0
   Compiling serde_spanned v1.1.1
   Compiling utf8_iter v1.0.4
   Compiling percent-encoding v2.3.2
   Compiling utf-8 v0.7.6
   Compiling toml_writer v1.1.1+spec-1.1.0
   Compiling log v0.4.29
   Compiling bitflags v1.3.2
   Compiling winnow v0.7.15
   Compiling dtoa v1.0.11
   Compiling tendril v0.4.3
   Compiling dtoa-short v0.3.5
   Compiling form_urlencoded v1.2.2
   Compiling idna v1.1.0
   Compiling uuid v1.23.0
   Compiling selectors v0.24.0
   Compiling string_cache v0.8.9
   Compiling phf v0.10.1
   Compiling indexmap v1.9.3
   Compiling phf v0.11.3
   Compiling aho-corasick v1.1.4
   Compiling ctor v0.2.9
   Compiling nodrop v0.1.14
   Compiling unic-common v0.9.0
   Compiling camino v1.2.2
   Compiling unic-char-range v0.9.0
   Compiling toml v0.9.12+spec-1.1.0
   Compiling regex-syntax v0.8.10
   Compiling alloc-no-stdlib v2.0.4
   Compiling matches v0.1.10
   Compiling alloc-stdlib v0.2.2
   Compiling unic-char-property v0.9.0
   Compiling unic-ucd-version v0.9.0
   Compiling servo_arc v0.2.0
   Compiling url v2.5.8
   Compiling winapi-util v0.1.11
   Compiling fxhash v0.2.1
   Compiling regex-automata v0.4.14
   Compiling phf v0.8.0
   Compiling match_token v0.1.0
   Compiling serde_derive_internals v0.29.1
   Compiling equivalent v1.0.2
   Compiling hashbrown v0.17.0
   Compiling hashbrown v0.12.3
   Compiling schemars v0.8.22
   Compiling indexmap v2.14.0
   Compiling schemars_derive v0.8.22
   Compiling regex v1.12.3
   Compiling html5ever v0.29.1
   Compiling same-file v1.0.6
   Compiling unic-ucd-ident v0.9.0
   Compiling brotli-decompressor v5.0.0
   Compiling cfb v0.7.3
   Compiling jsonptr v0.6.3
   Compiling cargo-platform v0.1.9
   Compiling dyn-clone v1.0.20
   Compiling dunce v1.0.5
   Compiling bytes v1.11.1
   Compiling brotli v8.0.2
   Compiling infer v0.19.0
   Compiling http v1.4.0
   Compiling json-patch v3.0.1
   Compiling cargo_metadata v0.19.2
   Compiling kuchikiki v0.8.8-speedreader
   Compiling urlpattern v0.3.0
   Compiling walkdir v2.5.0
   Compiling serde-untagged v0.1.9
   Compiling serde_with v3.11.0
    Checking once_cell v1.21.4
    Checking value-bag v1.12.0
   Compiling glob v0.3.3
   Compiling windows_x86_64_msvc v0.52.6
   Compiling version_check v0.9.5
   Compiling tauri-utils v2.8.3
    Checking pin-project-lite v0.2.17
   Compiling rustc_version v0.4.1
   Compiling libc v0.2.185
   Compiling vswhom-sys v0.1.3
   Compiling windows-targets v0.52.6
   Compiling windows-sys v0.59.0
   Compiling winreg v0.55.0
   Compiling option-ext v0.2.0
   Compiling dirs-sys v0.5.0
   Compiling time-core v0.1.8
   Compiling vswhom v0.1.0
   Compiling embed-resource v3.0.8
   Compiling heck v0.5.0
    Checking powerfmt v0.2.0
   Compiling num-conv v0.2.1
   Compiling crc32fast v1.5.0
   Compiling time-macros v0.2.27
    Checking deranged v0.5.8
   Compiling tauri-plugin v2.5.4
   Compiling tauri-winres v0.3.5
   Compiling dirs v6.0.0
   Compiling cargo_toml v0.22.3
    Checking mio v1.2.0
    Checking socket2 v0.6.3
   Compiling tokio-macros v2.7.0
    Checking windows-link v0.1.3
    Checking time v0.3.47
    Checking tokio v1.52.0
   Compiling tauri-build v2.5.6
    Checking tracing-core v0.1.36
   Compiling tracing-attributes v0.1.31
    Checking windows-strings v0.4.2
    Checking windows-result v0.3.4
   Compiling windows-interface v0.59.3
   Compiling windows-implement v0.60.2
    Checking bitflags v2.11.1
    Checking tracing v0.1.44
    Checking windows-core v0.61.2
   Compiling crossbeam-utils v0.8.21
    Checking windows-threading v0.1.0
   Compiling typenum v1.19.0
    Checking windows-future v0.2.1
    Checking windows-numerics v0.2.0
    Checking windows-collections v0.2.0
   Compiling cookie v0.18.1
   Compiling generic-array v0.14.7
    Checking windows v0.61.3
    Checking raw-window-handle v0.6.2
   Compiling simd-adler32 v0.3.9
   Compiling windows_x86_64_msvc v0.53.1
   Compiling webview2-com-sys v0.38.2
   Compiling adler2 v2.0.1
    Checking futures-core v0.3.32
   Compiling miniz_oxide v0.8.9
    Checking dpi v0.1.2
   Compiling tauri v2.10.3
   Compiling flate2 v1.1.9
    Checking windows-targets v0.53.5
   Compiling crypto-common v0.1.7
   Compiling block-buffer v0.10.4
   Compiling fdeflate v0.3.7
    Checking crossbeam-channel v0.5.15
   Compiling webview2-com-macros v0.8.1
    Checking windows-version v0.1.7
    Checking unicode-segmentation v1.13.2
   Compiling getrandom v0.3.4
    Checking futures-sink v0.3.32
   Compiling png v0.17.16
   Compiling digest v0.10.7
    Checking windows-sys v0.60.2
   Compiling wry v0.54.4
   Compiling tauri-runtime v2.10.1
    Checking zeroize v1.8.2
   Compiling cpufeatures v0.2.17
   Compiling sha2 v0.10.9
   Compiling ico v0.5.0
   Compiling ring v0.17.14
    Checking slab v0.4.12
    Checking mime v0.3.17
   Compiling base64 v0.22.1
   Compiling tauri-runtime-wry v2.10.1
   Compiling tauri-codegen v2.5.5
    Checking rustls-pki-types v1.14.0
    Checking keyboard-types v0.7.0
    Checking softbuffer v0.4.8
   Compiling serialize-to-javascript-impl v0.1.2
    Checking serialize-to-javascript v0.1.2
    Checking muda v0.17.2
    Checking window-vibrancy v0.6.0
   Compiling tauri-macros v2.5.5
    Checking tokio-util v0.7.18
    Checking http-body v1.0.1
   Compiling serde_repr v0.1.20
   Compiling futures-macro v0.3.32
   Compiling httparse v1.10.1
    Checking untrusted v0.9.0
   Compiling winapi v0.3.9
    Checking futures-task v0.3.32
    Checking futures-io v0.3.32
    Checking futures-util v0.3.32
    Checking schannel v0.1.29
    Checking try-lock v0.2.5
    Checking tower-service v0.3.3
   Compiling rustls v0.23.38
    Checking atomic-waker v1.1.2
    Checking h2 v0.4.13
    Checking want v0.3.1
    Checking rustls-webpki v0.103.13
    Checking futures-channel v0.3.32
    Checking windows-result v0.4.1
    Checking windows-strings v0.5.1
    Checking subtle v2.6.1
    Checking hyper v1.9.0
    Checking windows-registry v0.6.1
    Checking debugid v0.8.0
    Checking hex v0.4.3
    Checking ipnet v2.12.0
    Checking compression-core v0.4.31
    Checking sentry-types v0.34.0
    Checking compression-codecs v0.4.37
    Checking hyper-util v0.1.20
    Checking http-body-util v0.1.3
   Compiling tauri-plugin-fs v2.5.0
    Checking sync_wrapper v1.0.2
   Compiling native-tls v0.2.18
    Checking tower-layer v0.3.3
   Compiling windows_x86_64_msvc v0.48.5
    Checking tower v0.5.3
    Checking async-compression v0.4.41
    Checking tokio-rustls v0.26.4
    Checking rustls-native-certs v0.8.3
    Checking webpki-roots v1.0.7
    Checking encoding_rs v0.8.35
    Checking sentry-core v0.34.0
    Checking iri-string v0.7.12
    Checking hyper-rustls v0.27.9
   Compiling num-traits v0.2.19
   Compiling markup5ever v0.12.1
    Checking lazy_static v1.5.0
   Compiling litrs v1.0.0
    Checking tower-http v0.6.8
    Checking psl-types v2.0.11
    Checking publicsuffix v2.3.0
   Compiling document-features v0.2.12
    Checking windows-targets v0.48.5
    Checking tokio-native-tls v0.3.1
    Checking bstr v1.12.1
   Compiling findshlibs v0.10.2
   Compiling html5ever v0.27.0
   Compiling phf_codegen v0.10.0
    Checking ryu v1.0.23
   Compiling cfg_aliases v0.1.1
   Compiling rust_decimal v1.41.0
    Checking rustc-demangle v0.1.27
    Checking backtrace v0.3.76
   Compiling nix v0.28.0
    Checking serde_urlencoded v0.7.1
   Compiling selectors v0.25.0
    Checking cookie_store v0.22.0
    Checking hyper-tls v0.6.0
    Checking windows-sys v0.48.0
   Compiling tauri-plugin-dialog v2.7.0
   Compiling tauri-plugin-positioner v2.3.1
   Compiling tauri-plugin-window-state v2.4.1
   Compiling tauri-plugin-http v2.5.8
   Compiling tauri-plugin-log v2.8.0
   Compiling tauri-plugin-updater v2.10.1
   Compiling tauri-plugin-shell v2.3.5
   Compiling tauri-plugin-store v2.4.2
   Compiling tauri-plugin-process v2.3.1
   Compiling sentry-contexts v0.34.0
   Compiling ahash v0.8.12
   Compiling ntapi v0.4.3
    Checking arrayvec v0.7.6
   Compiling winreg v0.10.1
   Compiling rfd v0.16.0
    Checking reqwest v0.12.28
    Checking cssparser v0.31.2
    Checking sentry-backtrace v0.34.0
    Checking sharded-slab v0.1.7
    Checking rustls-platform-verifier v0.6.2
    Checking crossbeam-epoch v0.9.18
    Checking tracing-log v0.2.0
    Checking matchers v0.2.0
    Checking os_info v3.14.0
    Checking servo_arc v0.3.0
    Checking nu-ansi-term v0.50.3
    Checking grep-matcher v0.1.8
    Checking hostname v0.4.2
    Checking thread_local v1.1.9
    Checking utf8-width v0.1.8
    Checking unicode-width v0.2.2
    Checking fastrand v2.4.1
    Checking tracing-subscriber v0.3.23
    Checking tempfile v3.27.0
    Checking getopts v0.2.24
    Checking byte-unit v5.2.0
    Checking crossbeam-deque v0.8.6
    Checking reqwest v0.13.2
    Checking sentry-panic v0.34.0
    Checking sentry-debug-images v0.34.0
    Checking dirs-sys v0.4.1
    Checking globset v0.4.18
    Checking shared_library v0.1.9
    Checking encoding_rs_io v0.1.7
    Checking filedescriptor v0.8.3
    Checking serial2 v0.2.36
    Checking zip v4.6.1
    Checking shared_child v1.1.1
    Checking open v5.3.3
   Compiling TrixtyIDE v0.0.0 (D:\a\ide\ide\apps\desktop\src-tauri)
    Checking fern v0.7.1
    Checking os_pipe v1.2.3
    Checking filetime v0.2.27
    Checking shell-words v1.1.1
    Checking data-url v0.3.2
    Checking httpdate v1.0.3
    Checking minisign-verify v0.2.5
    Checking ego-tree v0.6.3
    Checking memmap2 v0.9.10
    Checking downcast-rs v1.2.1
    Checking unicode-width v0.1.13
    Checking portable-pty v0.9.0
    Checking tao v0.34.8
    Checking html2text v0.12.6
    Checking sysinfo v0.36.1
    Checking scraper v0.19.1
    Checking grep-searcher v0.1.16
    Checking sentry v0.34.0
    Checking notify v6.1.1
    Checking ignore v0.4.25
    Checking dirs v5.0.1
    Checking sentry-tracing v0.34.0
    Checking grep-regex v0.1.14
    Checking keyring v3.6.3
    Checking urlencoding v2.1.3
    Checking webview2-com v0.38.2
error: large size difference between variants
  --> src\discord_rpc.rs:24:1
   |
24 | / pub enum RpcMessage {
25 | |     UpdateActivity(Option<Activity>),
   | |     -------------------------------- the largest variant contains at least 296 bytes
26 | |     AcceptJoin(String),
   | |     ------------------ the second-largest variant contains at least 24 bytes
27 | |     RejectJoin(String),
28 | | }
   | |_^ the entire enum is at least 296 bytes
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#large_enum_variant
   = note: `-D clippy::large-enum-variant` implied by `-D warnings`
   = help: to override `-D warnings` add `#[allow(clippy::large_enum_variant)]`
help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum
   |
25 -     UpdateActivity(Option<Activity>),
25 +     UpdateActivity(Box<Option<Activity>>),
   |

error: this `match` can be collapsed into the outer `if let`
   --> src\discord_rpc.rs:153:53
    |
153 | / ...                   match evt {
154 | | ...                       "ACTIVITY_JOIN" | "ACTIVITY_SPECTATE" | "ACTIVITY_JOIN_REQUEST" => {
155 | | ...                           let _ = app_handle.emit("discord-rpc-event", v);
...   |
158 | | ...                   }
    | |_______________________^
    |
help: the outer pattern can be modified to include the inner pattern
   --> src\discord_rpc.rs:152:61
    |
152 | ...                   if let Some(evt) = v["evt"].as_str() {
    |                                   ^^^ replace this binding
153 | ...                       match evt {
154 | ...                           "ACTIVITY_JOIN" | "ACTIVITY_SPECTATE" | "ACTIVITY_JOIN_REQUEST" => {
    |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ with this pattern
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#collapsible_match
    = note: `-D clippy::collapsible-match` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::collapsible_match)]`

error: could not compile `TrixtyIDE` (lib) due to 2 previous errors

View full logs

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 1, 2026

Hi @jmaxdev, the quality checks have failed.

❌ Quality Checks Failed

Check Status
Dependencies ✅ Success
Sandbox Worker Bundle ✅ Success
Lint ✅ Success
Typecheck ✅ Success
Clippy ✅ Success
Format ❌ Failure
Vitest ⚪ Skipped
Cargo Test ⚪ Skipped
Full Log (Format)
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\cli.rs:67:
                 seen_double_dash = true;
                 continue;
             }
-            
+
             // Handle Discord RPC join flag
             if arg == "--discord-rpc-join-secret" {
                 if let Some(val) = iter.next() {
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\cli.rs:101:
                 workspace_candidate = Some(value.to_string());
                 continue;
             }
-            
+
             if arg.starts_with("--") {
                 continue;
             }
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\cli.rs:108:
         }
-        
+
         if workspace_candidate.is_none() {
             workspace_candidate = Some(arg.clone());
         }
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\discord_rpc.rs:1:
 use serde::{Deserialize, Serialize};
 use serde_json::{json, Value};
 
+use log::{error, info, warn};
+use tauri::{AppHandle, Emitter};
 use tokio::io::{AsyncReadExt, AsyncWriteExt};
 #[cfg(unix)]
 use tokio::net::UnixStream;
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\discord_rpc.rs:7:
-use uuid::Uuid;
-use log::{info, warn, error};
-use tauri::{AppHandle, Emitter};
 use tokio::sync::mpsc;
+use uuid::Uuid;
 
 #[cfg(windows)]
 use tokio::net::windows::named_pipe::ClientOptions;
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\discord_rpc.rs:49:
             #[cfg(windows)]
             IpcStream::Windows(s) => {
                 s.write_all(&header).await.map_err(|e| e.to_string())?;
-                s.write_all(payload.as_bytes()).await.map_err(|e| e.to_string())?;
+                s.write_all(payload.as_bytes())
+                    .await
+                    .map_err(|e| e.to_string())?;
             }
             #[cfg(unix)]
             IpcStream::Unix(s) => {
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\discord_rpc.rs:56:
                 s.write_all(&header).await.map_err(|e| e.to_string())?;
-                s.write_all(payload.as_bytes()).await.map_err(|e| e.to_string())?;
+                s.write_all(payload.as_bytes())
+                    .await
+                    .map_err(|e| e.to_string())?;
             }
         }
         Ok(())
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\discord_rpc.rs:80:
         match self {
             #[cfg(windows)]
             IpcStream::Windows(s) => {
-                s.read_exact(&mut payload).await.map_err(|e| e.to_string())?;
+                s.read_exact(&mut payload)
+                    .await
+                    .map_err(|e| e.to_string())?;
             }
             #[cfg(unix)]
             IpcStream::Unix(s) => {
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\discord_rpc.rs:87:
-                s.read_exact(&mut payload).await.map_err(|e| e.to_string())?;
+                s.read_exact(&mut payload)
+                    .await
+                    .map_err(|e| e.to_string())?;
             }
         }
 
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\discord_rpc.rs:114:
                 match Self::try_connect(&client_id).await {
                     Ok(mut stream) => {
                         info!("[Discord] Connected and handshaked.");
-                        
+
                         let _ = Self::do_subscribe(&mut stream, "ACTIVITY_JOIN").await;
                         let _ = Self::do_subscribe(&mut stream, "ACTIVITY_SPECTATE").await;
                         let _ = Self::do_subscribe(&mut stream, "ACTIVITY_JOIN_REQUEST").await;
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\discord_rpc.rs:139:
                                             "nonce": Uuid::new_v4().to_string()
                                         }).to_string(),
                                     };
-                                    
+
                                     if let Err(e) = stream.send(OpCode::Frame, &payload).await {
                                         error!("[Discord] Send failed: {}", e);
                                         break;
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\discord_rpc.rs:225:
             "cmd": "SUBSCRIBE",
             "evt": evt,
             "nonce": Uuid::new_v4().to_string()
-        }).to_string();
+        })
+        .to_string();
         stream.send(OpCode::Frame, &payload).await?;
         Ok(())
     }
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\discord_rpc.rs:232:
 
     pub fn set_activity(&self, activity: Option<Activity>) -> Result<(), String> {
         if let Some(tx) = &self.tx {
-            tx.send(RpcMessage::UpdateActivity(Box::new(activity))).map_err(|e| e.to_string())?;
+            tx.send(RpcMessage::UpdateActivity(Box::new(activity)))
+                .map_err(|e| e.to_string())?;
             Ok(())
         } else {
             Err("RPC not started".to_string())
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\discord_rpc.rs:241:
 
     pub fn accept_join_request(&self, user_id: String) -> Result<(), String> {
         if let Some(tx) = &self.tx {
-            tx.send(RpcMessage::AcceptJoin(user_id)).map_err(|e| e.to_string())?;
+            tx.send(RpcMessage::AcceptJoin(user_id))
+                .map_err(|e| e.to_string())?;
             Ok(())
         } else {
             Err("RPC not started".to_string())
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\discord_rpc.rs:250:
 
     pub fn reject_join_request(&self, user_id: String) -> Result<(), String> {
         if let Some(tx) = &self.tx {
-            tx.send(RpcMessage::RejectJoin(user_id)).map_err(|e| e.to_string())?;
+            tx.send(RpcMessage::RejectJoin(user_id))
+                .map_err(|e| e.to_string())?;
             Ok(())
         } else {
             Err("RPC not started".to_string())
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\lib.rs:1:
 mod about;
 mod cli;
+mod discord_rpc;
 mod error;
 mod fs_atomic;
 mod fs_guard;
Diff in \\?\D:\a\ide\ide\apps\desktop\src-tauri\src\lib.rs:6:
 mod fs_watcher;
 mod http;
 mod pty;
-mod discord_rpc;
-
 
 use error::redact_user_paths;
 

View full logs

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 1, 2026

Hi @jmaxdev, the quality checks have failed.

❌ Quality Checks Failed

Check Status
Dependencies ✅ Success
Sandbox Worker Bundle ✅ Success
Lint ✅ Success
Typecheck ✅ Success
Clippy ✅ Success
Format ✅ Success
Vitest ✅ Success
Cargo Test ❌ Failure
Full Log (Cargo Test)
   Compiling cfg-if v1.0.4
   Compiling windows-link v0.2.1
   Compiling memchr v2.8.0
   Compiling zerocopy v0.8.48
   Compiling getrandom v0.2.17
   Compiling itoa v1.0.18
   Compiling stable_deref_trait v1.2.1
   Compiling rand_core v0.6.4
   Compiling zerofrom v0.1.7
   Compiling yoke v0.8.2
   Compiling writeable v0.6.3
   Compiling zerovec v0.11.6
   Compiling litemap v0.8.2
   Compiling zerotrie v0.2.4
   Compiling icu_normalizer_data v2.1.1
   Compiling icu_properties_data v2.1.2
   Compiling tinystr v0.8.3
   Compiling potential_utf v0.1.5
   Compiling siphasher v1.0.2
   Compiling icu_locale_core v2.1.1
   Compiling icu_collections v2.1.1
   Compiling phf_shared v0.11.3
   Compiling zmij v1.0.21
   Compiling utf8_iter v1.0.4
   Compiling bytes v1.11.1
   Compiling fnv v1.0.7
   Compiling icu_provider v2.1.1
   Compiling icu_properties v2.1.2
   Compiling serde_core v1.0.228
   Compiling getrandom v0.4.2
   Compiling regex-syntax v0.8.10
   Compiling ppv-lite86 v0.2.21
   Compiling siphasher v0.3.11
   Compiling thiserror v1.0.69
   Compiling http v1.4.0
   Compiling rand_chacha v0.3.1
   Compiling rand v0.8.6
   Compiling scopeguard v1.2.0
   Compiling lock_api v0.4.14
   Compiling byteorder v1.5.0
   Compiling phf_generator v0.11.3
   Compiling alloc-no-stdlib v2.0.4
   Compiling new_debug_unreachable v1.0.6
   Compiling alloc-stdlib v0.2.2
   Compiling string_cache_codegen v0.5.4
   Compiling phf_macros v0.11.3
   Compiling brotli-decompressor v5.0.0
   Compiling phf_codegen v0.11.3
   Compiling phf_shared v0.10.0
   Compiling phf v0.11.3
   Compiling equivalent v1.0.2
   Compiling hashbrown v0.17.0
   Compiling precomputed-hash v0.1.1
   Compiling thiserror v2.0.18
   Compiling phf_generator v0.10.0
   Compiling getrandom v0.1.16
   Compiling windows-sys v0.61.2
   Compiling rand_core v0.5.1
   Compiling winnow v1.0.1
   Compiling indexmap v2.14.0
   Compiling mac v0.1.1
   Compiling darling_core v0.20.6
   Compiling toml_parser v1.1.2+spec-1.1.0
   Compiling futf v0.1.5
   Compiling semver v1.0.28
   Compiling toml_writer v1.1.1+spec-1.1.0
   Compiling utf-8 v0.7.6
   Compiling winnow v0.7.15
   Compiling tendril v0.4.3
   Compiling typeid v1.0.3
   Compiling rand_chacha v0.2.2
   Compiling rand_pcg v0.2.1
   Compiling phf_shared v0.8.0
   Compiling dtoa v1.0.11
   Compiling unic-char-range v0.9.0
   Compiling unic-common v0.9.0
   Compiling unic-ucd-version v0.9.0
   Compiling unic-char-property v0.9.0
   Compiling darling_macro v0.20.6
   Compiling dtoa-short v0.3.5
   Compiling rand v0.7.3
   Compiling darling v0.20.6
   Compiling dunce v1.0.5
   Compiling smallvec v1.15.1
   Compiling serde_with_macros v3.11.0
   Compiling unic-ucd-ident v0.9.0
   Compiling phf_generator v0.8.0
   Compiling parking_lot_core v0.9.12
   Compiling icu_normalizer v2.1.0
   Compiling bitflags v1.3.2
   Compiling idna_adapter v1.2.1
   Compiling parking_lot v0.12.5
   Compiling phf_codegen v0.8.0
   Compiling phf_macros v0.10.0
   Compiling serde_json v1.0.149
   Compiling anyhow v1.0.102
   Compiling serde v1.0.228
   Compiling markup5ever v0.14.1
   Compiling fxhash v0.2.1
   Compiling serde_spanned v1.1.1
   Compiling toml_datetime v0.7.5+spec-1.1.0
   Compiling phf v0.10.1
   Compiling string_cache v0.8.9
   Compiling toml v0.9.12+spec-1.1.0
   Compiling selectors v0.24.0
   Compiling idna v1.1.0
   Compiling uuid v1.23.0
   Compiling aho-corasick v1.1.4
   Compiling glob v0.3.3
   Compiling cssparser v0.29.6
   Compiling regex-automata v0.4.14
   Compiling servo_arc v0.2.0
   Compiling url v2.5.8
   Compiling winapi-util v0.1.11
   Compiling phf v0.8.0
   Compiling html5ever v0.29.1
   Compiling regex v1.12.3
   Compiling same-file v1.0.6
   Compiling cfb v0.7.3
   Compiling jsonptr v0.6.3
   Compiling erased-serde v0.4.10
   Compiling schemars v0.8.22
   Compiling serde-untagged v0.1.9
   Compiling cargo_metadata v0.19.2
   Compiling infer v0.19.0
   Compiling json-patch v3.0.1
   Compiling urlpattern v0.3.0
   Compiling walkdir v2.5.0
   Compiling kuchikiki v0.8.8-speedreader
   Compiling serde_with v3.11.0
   Compiling brotli v8.0.2
   Compiling once_cell v1.21.4
   Compiling value-bag v1.12.0
   Compiling percent-encoding v2.3.2
   Compiling log v0.4.29
   Compiling windows_x86_64_msvc v0.52.6
   Compiling pin-project-lite v0.2.17
   Compiling windows-targets v0.52.6
   Compiling rustc_version v0.4.1
   Compiling option-ext v0.2.0
   Compiling windows-sys v0.59.0
   Compiling form_urlencoded v1.2.2
   Compiling libc v0.2.185
   Compiling tauri-utils v2.8.3
   Compiling winreg v0.55.0
   Compiling embed-resource v3.0.8
   Compiling dirs-sys v0.5.0
   Compiling heck v0.5.0
   Compiling time-core v0.1.8
   Compiling num-conv v0.2.1
   Compiling powerfmt v0.2.0
   Compiling deranged v0.5.8
   Compiling time-macros v0.2.27
   Compiling dirs v6.0.0
   Compiling tauri-winres v0.3.5
   Compiling mio v1.2.0
   Compiling socket2 v0.6.3
   Compiling cargo_toml v0.22.3
   Compiling windows-link v0.1.3
   Compiling tokio v1.52.0
   Compiling tauri-plugin v2.5.4
   Compiling tauri-build v2.5.6
   Compiling time v0.3.47
   Compiling tracing-core v0.1.36
   Compiling adler2 v2.0.1
   Compiling crc32fast v1.5.0
   Compiling tracing v0.1.44
   Compiling windows-result v0.3.4
   Compiling windows-strings v0.4.2
   Compiling bitflags v2.11.1
   Compiling windows-core v0.61.2
   Compiling windows-threading v0.1.0
   Compiling windows-collections v0.2.0
   Compiling windows-numerics v0.2.0
   Compiling windows-future v0.2.1
   Compiling crossbeam-utils v0.8.21
   Compiling base64 v0.22.1
   Compiling windows v0.61.3
   Compiling raw-window-handle v0.6.2
   Compiling cookie v0.18.1
   Compiling webview2-com-sys v0.38.2
   Compiling futures-core v0.3.32
   Compiling windows_x86_64_msvc v0.53.1
   Compiling miniz_oxide v0.8.9
   Compiling dpi v0.1.2
   Compiling flate2 v1.1.9
   Compiling windows-targets v0.53.5
   Compiling crossbeam-channel v0.5.15
   Compiling tauri v2.10.3
   Compiling windows-version v0.1.7
   Compiling futures-sink v0.3.32
   Compiling unicode-segmentation v1.13.2
   Compiling png v0.17.16
   Compiling windows-sys v0.60.2
   Compiling zeroize v1.8.2
   Compiling sha2 v0.10.9
   Compiling ico v0.5.0
   Compiling getrandom v0.3.4
   Compiling mime v0.3.17
   Compiling slab v0.4.12
   Compiling tauri-codegen v2.5.5
   Compiling rustls-pki-types v1.14.0
   Compiling keyboard-types v0.7.0
   Compiling softbuffer v0.4.8
   Compiling serialize-to-javascript v0.1.2
   Compiling tauri-macros v2.5.5
   Compiling window-vibrancy v0.6.0
   Compiling muda v0.17.2
   Compiling tokio-util v0.7.18
   Compiling http-body v1.0.1
   Compiling futures-io v0.3.32
   Compiling futures-task v0.3.32
   Compiling untrusted v0.9.0
   Compiling ring v0.17.14
   Compiling futures-util v0.3.32
   Compiling schannel v0.1.29
   Compiling tower-service v0.3.3
   Compiling simd-adler32 v0.3.9
   Compiling atomic-waker v1.1.2
   Compiling try-lock v0.2.5
   Compiling want v0.3.1
   Compiling h2 v0.4.13
   Compiling rustls-webpki v0.103.13
   Compiling httparse v1.10.1
   Compiling winapi v0.3.9
   Compiling futures-channel v0.3.32
   Compiling windows-result v0.4.1
   Compiling windows-strings v0.5.1
   Compiling subtle v2.6.1
   Compiling windows-registry v0.6.1
   Compiling rustls v0.23.38
   Compiling hyper v1.9.0
   Compiling debugid v0.8.0
   Compiling ipnet v2.12.0
   Compiling compression-core v0.4.31
   Compiling hex v0.4.3
   Compiling sentry-types v0.34.0
   Compiling compression-codecs v0.4.37
   Compiling hyper-util v0.1.20
   Compiling http-body-util v0.1.3
   Compiling tauri-plugin-fs v2.5.0
   Compiling sync_wrapper v1.0.2
   Compiling tower-layer v0.3.3
   Compiling tower v0.5.3
   Compiling sentry-core v0.34.0
   Compiling async-compression v0.4.41
   Compiling tokio-rustls v0.26.4
   Compiling rustls-native-certs v0.8.3
   Compiling webpki-roots v1.0.7
   Compiling encoding_rs v0.8.35
   Compiling iri-string v0.7.12
   Compiling hyper-rustls v0.27.9
   Compiling tower-http v0.6.8
   Compiling native-tls v0.2.18
   Compiling windows_x86_64_msvc v0.48.5
   Compiling markup5ever v0.12.1
   Compiling lazy_static v1.5.0
   Compiling psl-types v2.0.11
   Compiling windows-targets v0.48.5
   Compiling publicsuffix v2.3.0
   Compiling tokio-native-tls v0.3.1
   Compiling bstr v1.12.1
   Compiling phf_codegen v0.10.0
   Compiling ryu v1.0.23
   Compiling rustc-demangle v0.1.27
   Compiling backtrace v0.3.76
   Compiling serde_urlencoded v0.7.1
   Compiling selectors v0.25.0
   Compiling hyper-tls v0.6.0
   Compiling cookie_store v0.22.0
   Compiling num-traits v0.2.19
   Compiling windows-sys v0.48.0
   Compiling tao v0.34.8
   Compiling tauri-plugin-window-state v2.4.1
   Compiling tauri-plugin-log v2.8.0
   Compiling webview2-com v0.38.2
   Compiling tauri-plugin-process v2.3.1
   Compiling tauri-plugin-updater v2.10.1
   Compiling tauri-plugin-http v2.5.8
   Compiling tauri-plugin-store v2.4.2
   Compiling wry v0.54.4
   Compiling tauri-runtime v2.10.1
   Compiling tauri-runtime-wry v2.10.1
   Compiling tauri-plugin-positioner v2.3.1
   Compiling tauri-plugin-shell v2.3.5
   Compiling tauri-plugin-dialog v2.7.0
   Compiling sentry-contexts v0.34.0
   Compiling arrayvec v0.7.6
   Compiling rust_decimal v1.41.0
   Compiling reqwest v0.12.28
   Compiling findshlibs v0.10.2
   Compiling html5ever v0.27.0
   Compiling sentry-backtrace v0.34.0
   Compiling sharded-slab v0.1.7
   Compiling rustls-platform-verifier v0.6.2
   Compiling crossbeam-epoch v0.9.18
   Compiling tracing-log v0.2.0
   Compiling matchers v0.2.0
   Compiling os_info v3.14.0
   Compiling nu-ansi-term v0.50.3
   Compiling cssparser v0.31.2
   Compiling grep-matcher v0.1.8
   Compiling servo_arc v0.3.0
   Compiling thread_local v1.1.9
   Compiling hostname v0.4.2
   Compiling utf8-width v0.1.8
   Compiling unicode-width v0.2.2
   Compiling fastrand v2.4.1
   Compiling getopts v0.2.24
   Compiling tempfile v3.27.0
   Compiling byte-unit v5.2.0
   Compiling tracing-subscriber v0.3.23
   Compiling crossbeam-deque v0.8.6
   Compiling reqwest v0.13.2
   Compiling nix v0.28.0
   Compiling sentry-panic v0.34.0
   Compiling sentry-debug-images v0.34.0
   Compiling ahash v0.8.12
   Compiling ntapi v0.4.3
   Compiling winreg v0.10.1
   Compiling rfd v0.16.0
   Compiling dirs-sys v0.4.1
   Compiling globset v0.4.18
   Compiling shared_library v0.1.9
   Compiling encoding_rs_io v0.1.7
   Compiling filedescriptor v0.8.3
   Compiling serial2 v0.2.36
   Compiling shared_child v1.1.1
   Compiling TrixtyIDE v0.0.0 (D:\a\ide\ide\apps\desktop\src-tauri)
   Compiling zip v4.6.1
   Compiling fern v0.7.1
   Compiling os_pipe v1.2.3
   Compiling open v5.3.3
   Compiling filetime v0.2.27
   Compiling shell-words v1.1.1
   Compiling memmap2 v0.9.10
   Compiling downcast-rs v1.2.1
   Compiling data-url v0.3.2
   Compiling httpdate v1.0.3
   Compiling ego-tree v0.6.3
   Compiling unicode-width v0.1.13
   Compiling minisign-verify v0.2.5
   Compiling html2text v0.12.6
   Compiling scraper v0.19.1
   Compiling sentry v0.34.0
   Compiling portable-pty v0.9.0
   Compiling grep-searcher v0.1.16
   Compiling notify v6.1.1
   Compiling sysinfo v0.36.1
   Compiling ignore v0.4.25
   Compiling dirs v5.0.1
   Compiling sentry-tracing v0.34.0
   Compiling grep-regex v0.1.14
   Compiling keyring v3.6.3
   Compiling urlencoding v2.1.3
error[E0308]: mismatched types
   --> src\cli.rs:193:25
    |
193 |         assert_eq!(out, CliWorkspace::Empty);
    |                         ^^^^^^^^^^^^^^^^^^^ expected `CliResult`, found `CliWorkspace`

error[E0308]: mismatched types
   --> src\cli.rs:201:13
    |
200 |         match out {
    |               --- this expression has type `CliResult`
201 |             CliWorkspace::Path(p) => assert!(p.is_dir()),
    |             ^^^^^^^^^^^^^^^^^^^^^ expected `CliResult`, found `CliWorkspace`
    |
help: you might have meant to use field `workspace` whose type is `cli::CliWorkspace`
    |
200 |         match out.workspace {
    |                  ++++++++++

error[E0308]: mismatched types
   --> src\cli.rs:211:13
    |
210 |         match out {
    |               --- this expression has type `CliResult`
211 |             CliWorkspace::Path(p) => assert!(p.is_dir()),
    |             ^^^^^^^^^^^^^^^^^^^^^ expected `CliResult`, found `CliWorkspace`
    |
help: you might have meant to use field `workspace` whose type is `cli::CliWorkspace`
    |
210 |         match out.workspace {
    |                  ++++++++++

error[E0308]: mismatched types
   --> src\cli.rs:222:13
    |
221 |         match out {
    |               --- this expression has type `CliResult`
222 |             CliWorkspace::Path(p) => assert!(p.is_dir()),
    |             ^^^^^^^^^^^^^^^^^^^^^ expected `CliResult`, found `CliWorkspace`
    |
help: you might have meant to use field `workspace` whose type is `cli::CliWorkspace`
    |
221 |         match out.workspace {
    |                  ++++++++++

error[E0308]: mismatched types
   --> src\cli.rs:233:13
    |
232 |         match out {
    |               --- this expression has type `CliResult`
233 |             CliWorkspace::Path(p) => assert_eq!(p, canonical_cwd),
    |             ^^^^^^^^^^^^^^^^^^^^^ expected `CliResult`, found `CliWorkspace`
    |
help: you might have meant to use field `workspace` whose type is `cli::CliWorkspace`
    |
232 |         match out.workspace {
    |                  ++++++++++

error[E0308]: mismatched types
   --> src\cli.rs:246:13
    |
245 |         match out {
    |               --- this expression has type `CliResult`
246 |             CliWorkspace::Path(p) => {
    |             ^^^^^^^^^^^^^^^^^^^^^ expected `CliResult`, found `CliWorkspace`
    |
help: you might have meant to use field `workspace` whose type is `cli::CliWorkspace`
    |
245 |         match out.workspace {
    |                  ++++++++++

error[E0308]: mismatched types
   --> src\cli.rs:260:31
    |
260 |         assert!(matches!(out, CliWorkspace::Invalid { .. }));
    |                          ---  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `CliResult`, found `CliWorkspace`
    |                          |
    |                          this expression has type `CliResult`
    |
help: you might have meant to use field `workspace` whose type is `cli::CliWorkspace`
    |
260 |         assert!(matches!(out.workspace, CliWorkspace::Invalid { .. }));
    |                             ++++++++++

error[E0308]: mismatched types
   --> src\cli.rs:270:13
    |
269 |         match out {
    |               --- this expression has type `CliResult`
270 |             CliWorkspace::Invalid { reason, .. } => {
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `CliResult`, found `CliWorkspace`
    |
help: you might have meant to use field `workspace` whose type is `cli::CliWorkspace`
    |
269 |         match out.workspace {
    |                  ++++++++++

error[E0308]: mismatched types
   --> src\cli.rs:281:13
    |
280 |         match out {
    |               --- this expression has type `CliResult`
281 |             CliWorkspace::Invalid { reason, .. } => {
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `CliResult`, found `CliWorkspace`
    |
help: you might have meant to use field `workspace` whose type is `cli::CliWorkspace`
    |
280 |         match out.workspace {
    |                  ++++++++++

error[E0308]: mismatched types
   --> src\cli.rs:292:13
    |
291 |         match out {
    |               --- this expression has type `CliResult`
292 |             CliWorkspace::Invalid { reason, .. } => {
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `CliResult`, found `CliWorkspace`
    |
help: you might have meant to use field `workspace` whose type is `cli::CliWorkspace`
    |
291 |         match out.workspace {
    |                  ++++++++++

error[E0308]: mismatched types
   --> src\cli.rs:313:13
    |
312 |         match out {
    |               --- this expression has type `CliResult`
313 |             CliWorkspace::Path(p) => assert!(p.is_dir()),
    |             ^^^^^^^^^^^^^^^^^^^^^ expected `CliResult`, found `CliWorkspace`
    |
help: you might have meant to use field `workspace` whose type is `cli::CliWorkspace`
    |
312 |         match out.workspace {
    |                  ++++++++++

error[E0308]: mismatched types
   --> src\cli.rs:324:31
    |
324 |         assert!(matches!(out, CliWorkspace::Invalid { .. }));
    |                          ---  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `CliResult`, found `CliWorkspace`
    |                          |
    |                          this expression has type `CliResult`
    |
help: you might have meant to use field `workspace` whose type is `cli::CliWorkspace`
    |
324 |         assert!(matches!(out.workspace, CliWorkspace::Invalid { .. }));
    |                             ++++++++++

For more information about this error, try `rustc --explain E0308`.
error: could not compile `TrixtyIDE` (lib test) due to 12 previous errors

View full logs

@jmaxdev jmaxdev merged commit 9e7d91e into main May 1, 2026
9 checks passed
jmaxdev added a commit that referenced this pull request May 2, 2026
* fixed lint errors (#308)

* feat: add Sentry error tracking (#287)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Revert "feat: add Sentry error tracking (#287)" (#291)

This reverts commit c779d52.

* feat: cloud AI streaming for OpenAI / Anthropic / Gemini / OpenRouter (#293)

PR #284 shipped cloud chat as a single-shot non-streaming bridge —
every reply landed atomically once the request finished. That made the
cloud branch feel slower than Ollama, where the chat panel renders
tokens as they arrive. This wires SSE streaming through a new
`cloud_proxy_stream` Tauri command so the UX matches.

Rust
- New `cloud_proxy_stream` and `cloud_proxy_cancel` commands behind the
  existing `validate_cloud_proxy_request` policy. The command spawns a
  detached tokio task, registers a oneshot cancel handle keyed by the
  caller-supplied `streamId`, and emits `cloud-stream` events as
  `{ kind: "data" | "done" | "error", data?, error? }`.
- New `split_sse_events` parser splits the response buffer on
  `\n\n` / `\r\n\r\n`, joins multi-`data:` lines per event, and drops
  `event:` / `id:` / `retry:` / comment lines. The Rust side stays
  provider-agnostic; the per-provider JSON shape is parsed in TS.
- The `data: [DONE]` sentinel that OpenAI / OpenRouter use is collapsed
  to a structured `done` event so callers don't have to special-case it.
  Anthropic and Gemini close the connection silently on completion;
  the task synthesises a `done` event on EOF so the awaiter resolves
  uniformly.
- Total stream size capped by the same `CLOUD_PROXY_MAX_BODY` limit
  (16 MiB) that `cloud_proxy` uses, and per-request timeout uses
  `CLOUD_PROXY_TIMEOUT_SECS` (60 s). Cancellation tears the task down
  via the oneshot.
- 10 unit tests for `split_sse_events` covering single events, multi-line
  data joining, CRLF separators, partial-event buffering, the [DONE]
  sentinel, and the SSE-spec single-leading-space rule.

TypeScript
- New `streamCloudChat(req, onDelta)` mirroring `streamOllamaChat`'s
  shape: subscribes to `cloud-stream`, filters by `streamId`, parses
  the provider-specific delta, and resolves with the full text on done.
- Provider-specific delta extractors split into
  `extractStreamDelta(provider, raw)`:
  - OpenAI / OpenRouter: `choices[0].delta.content`
  - Anthropic: `content_block_delta` events with `delta.type === "text_delta"`
    only — `message_start`, `content_block_start`, `ping`, `message_stop`,
    `input_json_delta` (future tool deltas) are ignored
  - Gemini: `candidates[*].content.parts[*].text` joined
- The matching `extractFullResponse` is exported and unit-tested so the
  non-streaming `cloudChat` shares the same parsing surface.
- The provider URL / headers / body builder is consolidated into
  `buildProviderRequest(req, stream)`. Gemini's URL flips between
  `:generateContent` and `:streamGenerateContent?alt=sse`; the others
  just toggle `stream` in the body.
- 12 unit tests covering each provider's streaming and full-response
  branches, including the role-only chunk that OpenAI sends as the
  first delta and Anthropic's housekeeping events.

UI wiring
- `AiChatComponent` cloud branch now uses `streamCloudChat` with the
  same `placeholderPushed` / `appendToLastAiMessage` pattern the Ollama
  branch uses. If the stream fails before any content arrives we still
  render a clean error bubble; if it fails mid-stream we append the
  error inline so the user sees how far the model got.
- Tools / agent mode for cloud providers stays out of scope. Each
  provider has its own tool-call shape (`tool_calls` for OpenAI,
  `tool_use` blocks for Anthropic, `functionDeclarations` for Gemini)
  and that lands in the next PR.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 (was 113 — added 12 provider tests).
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 (was 102 — added 10 SSE-parser tests).

* feat: store cloud AI provider keys in OS keychain (#294)

PR #284 shipped multi-provider keys behind a master switch and PR #286
added a banner warning that those keys lived in `settings.json`
plaintext. This wires the OS native secret store (review item I1) so
the keys never touch disk in clear text.

Backing store
- macOS: Keychain
- Windows: Credential Manager
- Linux: Secret Service / kwallet

The `keyring` crate (3.x) handles per-platform shimming. Pinned to 3.x
because keyring 4.x pulls `turso` (an embedded SQLite engine) and
`bon-macros` as transitive deps for its DB-backed unified store, none
of which we use; our `Entry::new` flow only needs the native OS
keychain backends that 3.x exposes by default.

Rust
- Four new commands behind a hard-coded provider allow-list
  (`openai` / `anthropic` / `gemini` / `openrouter`):
  `set_provider_secret`, `get_provider_secret`, `clear_provider_secret`,
  `has_provider_secret`. Service name is fixed at `trixty.ide` so
  entries are namespaced to the app and not visible to other tooling.
- `validate_secret_provider` rejects any string outside the allow-list
  so a renderer XSS can't probe arbitrary keychain entries.
- `keyring::Error::NoEntry` is mapped to `Ok(None)` / `Ok(false)` /
  `Ok(())` (idempotent clear), so the UI's "never configured" /
  "remove" paths don't surface false-positive error toasts.

TypeScript
- New `src/api/providerSecrets.ts` thin wrapper exposing
  `setProviderSecret` / `getProviderSecret` / `hasProviderSecret` /
  `clearProviderSecret` plus the `SECRET_PROVIDERS` list. Empty
  strings round-trip as "no key" — `setProviderSecret(p, "")` clears
  the entry, mirroring the way the old plaintext field treated `""`.
- Tauri-binding map extended with the four commands.

UI
- `ProviderKeysPanel` now loads each provider's key from the keychain
  on mount, holds it in component state for the reveal toggle and
  edit flow, and persists changes via a 500 ms debounced
  `setProviderSecret` (also flushed on blur). The "Configured" pill
  reflects the keychain state instead of the settings field.
- Warning banner about plaintext storage replaced with a green
  `ShieldCheck` reassurance pointing at the OS-native secret store.
- `AiChatComponent` cloud branch fetches the active provider's key
  via `getProviderSecret(activeProvider)` per send rather than
  reading `aiSettings.providerKeys`. Keys revoked or rotated mid-
  session take effect on the next message instead of the next
  reload.
- `keyForProvider` and the `ProviderKeys` import are removed from
  `client.ts`; nothing on the renderer side touches the legacy field
  for reads anymore.

Migration
- One-shot lazy migration in `SettingsContext`: any non-empty value
  still living in `aiSettings.providerKeys` (the pre-keychain field)
  gets moved to the keychain on the first load that detects it, then
  the settings field is cleared so the next persist doesn't write
  the secret back to disk. Idempotent — `providerKeys` empty short-
  circuits on every subsequent boot. Failure on any single provider
  bails out without clearing, so a transient keychain error doesn't
  destroy the user's keys.

Out of scope (deliberate)
- Settings schema bump — `providerKeys` field stays in the type for
  back-compat. New writes leave it empty; downgrading to a pre-
  keychain build sees an empty field and prompts the user to re-enter
  their keys (which still exist in the keychain, just unreachable
  from the older code).
- macOS Touch ID prompt suppression. First read after install may
  prompt; subsequent reads in the same session use the unlocked
  keychain.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 passing.
- pnpm build (Next 16 export) clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 passing.
- Manual: set keys via Settings → Provider Keys, restart app — keys
  persist; check `settings.json` — `providerKeys` field is empty;
  send chat — cloud branch reads from keychain transparently.

* feat: cloud AI tool calling and agent mode for OpenAI / Anthropic / Gemini / OpenRouter (#295)

PR #284 shipped multi-provider cloud chat as text-only. PR #293 added
streaming. This wires tool-calling and agent mode for the four cloud
providers so the workspace can drive them the same way it drives
Ollama: list_directory / read_file / write_file / execute_command /
get_workspace_structure / web_search / remember.

Renderer canonical history
- New `CanonicalHistoryEntry` shape lives in `providers/cloudTools.ts`
  alongside `ToolDefinition` and `UnifiedToolCall`. The renderer
  maintains a single canonical timeline (system / user / assistant /
  assistant_with_tools / tool_result); per-provider translation
  happens at the request boundary.
- `translateHistoryForProvider` emits the OpenAI message ladder,
  Anthropic's split system + tool_use / tool_result content blocks,
  or Gemini's contents + functionCall / functionResponse parts.
- `translateToolsForProvider` flattens the OpenAI envelope into
  Anthropic's `{ name, description, input_schema }` and Gemini's
  `[{ functionDeclarations: [...] }]`. OpenAI / OpenRouter pass
  through unchanged.
- `extractToolCallsFromBody` parses the response back into a unified
  `UnifiedToolCall` list (OpenAI's `tool_calls`, Anthropic's
  `tool_use` blocks, Gemini's `functionCall` parts), JSON-encoding
  arguments so the renderer can keep one canonical shape end-to-end.

Per-provider request
- New `cloudAgentChat(req)` in `providers/client.ts` — single-shot
  per turn. Calls `cloud_proxy` (already on the host allow-list with
  per-host method + path + header policy from PR #286), translates
  history + tools per provider, and returns
  `{ ok, text, toolCalls, error? }`.
- Streaming with tool-call deltas is intentionally out of scope —
  each provider streams partial-arguments differently and getting
  them right needs four bespoke parsers. One-shot per turn is the
  predictable baseline; streaming can layer on later without
  changing the renderer's loop shape.

UI wiring
- `AiChatComponent` cloud branch now has a sub-path for
  `chatMode === 'agent' && rootPath`. Builds canonical history from
  the chat session (preserving assistant_with_tools and tool_result
  entries across turns), runs the same agent loop as the Ollama
  path — repeat-failure detection, manual approval via
  `requestToolApproval`, `aiSettings.alwaysAllowTools`,
  `MAX_ITERATIONS = aiSettings.deepMode ? 15 : 5`, planner-mode
  gating left intact for the Ollama path. Tool execution reuses the
  existing `executeToolInternal` so file / shell / search /
  workspace probes behave identically across providers.
- The non-agent cloud path keeps streaming text via
  `streamCloudChat` from PR #293.

Tests
- 17 unit tests for `cloudTools.ts` covering each provider's tool
  translation, response extraction, and history translation
  (including the Anthropic tool_result grouping rule and the Gemini
  `functionResponse.response` object-wrapping fallback).

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 (was 125 — added 17 cloudTools tests).
- pnpm build (Next 16 export) clean.
- cargo build / clippy / fmt --check clean. cargo test --lib: 112 / 112.

* feat: detach bottom panel into a floating window (#296)

PR #282 added detach for right-panel views. Bottom panel was the next
deferred item. Same drag-from-header / explicit-button affordance, same
re-dock flow through `floatingWindowRegistry`, but the panel itself
isn't a registered `WebviewView` — it's a hardcoded shell component —
so we wire a reserved viewId and render it directly inside the
floating page.

Registry
- `DetachablePanel` now accepts `"bottom"` alongside `"right"` / `"left"`.
- New `BOTTOM_PANEL_VIEW_ID` constant (`trixty.builtin.bottom-panel`)
  exported from the registry. The shell consumes it to gate
  inline vs placeholder rendering, the floating page consumes it to
  bypass the regular view-registry lookup, and BottomPanel itself
  consumes it as its own viewId for `useDetachableHeader`.

Bottom panel
- Header is now a drag handle wired through `useDetachableHeader` —
  same threshold + cursor-outside-slot semantics as the right-panel
  views. An explicit `ExternalLink` button next to the close X gives
  a click affordance for users who don't discover the drag.
- New `isFloating` prop (default `false`). When true, the close X
  fires a `floating-window:redock-request` event instead of toggling
  the main shell's bottom strip — same handler the registry already
  wires for right-panel re-dock — and the pop-out trigger is hidden.
- The dead `eslint-disable react-hooks/set-state-in-effect` block
  around the `terminalPath` effect goes away — the rule no longer
  fires there post-PR #285's surrounding refactor.

Floating page
- `viewId === BOTTOM_PANEL_VIEW_ID` short-circuits the regular
  `useRegisteredView` path and renders `<BottomPanel isFloating />`
  inside the same `FloatingTitleBar` shell the other views use.
  Title resolves from `panel.bottom.terminal_tabs`; icon stays
  consistent with the inline header.

Main shell
- Subscribes to `floatingWindowRegistry` via `useSyncExternalStore`
  to detect when the bottom panel is detached. The
  `<ResizablePanel id="bottom">` slot stays mounted (so the layout
  preset / resize history doesn't change shape mid-detach) and we
  swap its body to a `BottomPanelDetachedPlaceholder`. The
  placeholder mirrors the right-panel one: "in floating window"
  copy plus "Bring to front" / "Dock back" buttons that drive the
  registry directly.

Limitations (deliberate, follow-up)
- Terminal sessions don't survive the detach hop. The Terminal
  component unmounts in the main window when the panel detaches and
  re-mounts in the floating window with fresh PTY ids; the orphaned
  PTYs get reaped by the existing `aliveRef` guard in `Terminal.tsx`.
  Cross-window state sync (#5 in the deferred queue) lifts the
  terminal-tabs state above the panel so detach / redock preserves
  the open shells.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build (Next 16 export): /, /_not-found, /floating prerender clean.
- Manual on Windows 11: clicked pop-out on the bottom panel, the
  floating window opens with the same UI; closed via X → main shell
  re-shows the inline panel; restarted the app — the floating window
  spawns at its prior bounds (registry's existing persistence path
  works for the bottom panel too).

* feat: cross-window chat history sync between main and floating windows (#297)

Each Tauri WebviewWindow runs its own JS realm, so detaching the AI
chat panel into a floating window today gave you two independent
\`ChatContext\` instances — sending a message in the float left the
main shell stuck on the prior conversation, and switching sessions
in either window had no effect on the other. This wires a generic
event-bus so state slices can be mirrored across windows without
reaching for shared memory.

Sync layer
- New \`src/api/crossWindowSync.ts\` exposes a tiny pub-sub layer over
  Tauri events. \`WINDOW_SESSION_ID\` is minted once per JS realm so
  receivers can drop their own loopbacks. \`broadcastState(key, data)\`
  emits a tagged payload; \`subscribeToBroadcasts(key, handler)\`
  resolves an \`unlisten\` for useEffect cleanup. Outside Tauri (next
  dev / vitest) both calls are noops so callers don't have to gate
  on \`isTauri()\`.

Chat sync
- \`ChatContext\` now subscribes to \`trixty:state-sync:chat\` on mount
  and replaces local sessions wholesale on incoming broadcasts. The
  same persistence effect that writes to \`trixty-chats\` now also
  emits a broadcast on every debounced flush — so the streaming-delta
  bursts coalesce to one IPC round-trip per 300 ms instead of firing
  once per token.
- \`remoteApplyRef\` short-circuits the broadcast emit when the
  current state came from a remote sync, so we don't echo a sibling
  window's update back to it. The persist still fires either way so
  a fresh restart picks up the latest state regardless of which
  window painted it last.

Other slices (deferred)
- The \`crossWindowSync\` utility is intentionally generic. Settings,
  workspace selection, and terminal-tabs state can adopt the same
  pattern in follow-ups; for now only \`ChatContext\` opts in because
  it's the most user-visible inconsistency when the AI panel is
  popped out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: detached the AI chat panel, sent a message in
  the float — the main shell's chat list updated within one debounce
  tick. Switched sessions in main → float caught up on next tick.
  Re-docked → no duplicate or stale messages remain.

* feat: JSON Crack-style graph view for .json files (#298)

PR #285 shipped Tree and Form visual surfaces for JSON / package.json
files. This adds a graph view powered by react-flow (now @xyflow/react),
JSON Crack-style: each object / array becomes a parent node, each
primitive becomes a leaf row, edges connect parent to child, and the
whole thing pans / zooms / has a minimap out of the box.

Visual surface
- New `JsonGraphEditor.tsx` walks the parsed JSON recursively to build
  react-flow nodes + edges. Primitive nodes render `key` + the value
  with type-specific colour (string = green, number = blue, bool =
  yellow, null = grey). Container nodes render `key` + a `[ N items ]`
  / `{ N keys }` summary. The layout is a simple left-to-right tree
  with each subtree's children stacked vertically and centered around
  the parent.
- 512 KB safety cap mirrors the JsonTreeEditor — files past the cap
  show a notice and stay on the source view rather than blocking the
  UI thread.
- Read-only by design. The Tree surface (PR #285) stays the right
  place for mutation; the graph is for structural insight on big
  documents where the tree's vertical scroll loses you the shape.

Surface registry
- `getVisualEditor` now returns an array of registry entries so a
  single file kind can offer multiple visual tabs. Generic `.json`
  registers Tree + Graph; `package.json` registers Form + Graph;
  `.env` keeps Table only. Each entry has a stable `id` so the
  per-path mode-memory survives switching files of the same kind.
- `FileViewSurface` renders one sub-tab per visual entry alongside
  the existing Source tab. Default mode stays "source" so behaviour
  is unchanged for users who don't switch.

Dependencies
- `@xyflow/react ^12.10.2` added to `apps/desktop/package.json`.
  Same package family that powers JSON Crack, n8n, Reactflow.dev's
  own demos. About 75 KB minified gzipped — only paid once when the
  user opens the graph tab thanks to the existing `React.lazy`
  registration in `getVisualEditor`.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean — the lazy
  import keeps `@xyflow/react` off the boot graph for users who never
  open a JSON file.

* feat: drag-to-reorder rows in .env and package.json visual editors (#299)

Issue #264 shipped tabular surfaces for .env and package.json. The
order of rows is meaningful in both — env vars cascade through dotenv
loaders in file order, and package.json scripts / deps are usually
ordered to make the file readable. Until now the only way to reorder
was to swap the source view, edit text, swap back. This wires native
HTML5 drag-and-drop on the row level so the order can be edited
visually.

Hook
- New `src/hooks/useDragReorder.ts` exposes a generic
  `useDragReorder({ items, getId, onReorder, groupKey? })` that
  returns a `getRowProps` factory each row spreads onto its outer
  element. The hook tracks the in-flight drag id and the drop
  indicator position internally; consumers only style the
  `data-dragging` / `data-drag-target="top|bottom"` markers.
- Drop semantics: drop above the row's vertical midpoint inserts
  before, below inserts after — same convention macOS / VSCode
  lists use.
- Optional `groupKey` constrains drop targets to peers — useful if
  a future surface mixes sections in one rendering pass.

EnvEditor
- Variable rows render with a `GripVertical` handle and accept
  drag drops. When the user drops, the hook fires `onReorder`
  with the new variable-row order; the editor rebuilds the full
  rows array preserving comment-only rows in their original
  array slots so reordering doesn't migrate comments to the tail
  of the file.

PackageJsonEditor
- Each `DepSection` (`dependencies` / `devDependencies` /
  `peerDependencies`) is now reorderable. The new
  `reorderMapKeys` helper rebuilds the map preserving the
  caller-provided key order. JS keeps insertion order for string
  keys, so `JSON.stringify` writes the new order to disk
  exactly. Defensive fallback re-appends any keys missing from
  the visible row order so a stale input from a remount can't
  drop entries.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: opened `apps/desktop/.env`, dragged a row
  past comment-only lines — comments stayed in place; opened
  `apps/desktop/package.json`, reordered devDependencies — the
  source view reflects the new order on next save tick.

* feat: open another workspace in a new window via Ctrl+Shift+N (#300)

Two repos side-by-side without context-switching. Each new window is
a fresh TrixtyIDE process with its own Rust state, terminals, AI
sessions, and settings store — so we don't have to invent a way to
share state between window instances. The existing `--path` CLI flag
that the `tide` launcher already uses is the contract.

Rust
- New `spawn_workspace_instance(path)` Tauri command resolves
  `current_exe()`, canonicalises the user-supplied folder, and
  spawns a detached `<binary> --path <folder>` process. Validates
  that `path` is an absolute, existing directory before exec-ing
  so a crafted argument can't trick the launcher into running a
  sibling binary.
- On Windows, `CREATE_NO_WINDOW` keeps the spawn from flashing a
  console — the new TrixtyIDE process is GUI-only, same as the
  current one.

UI
- New `Ctrl+Shift+N` shortcut in the main shell. Opens the same
  folder picker `Ctrl+O` uses (`@tauri-apps/plugin-dialog`), then
  invokes `spawn_workspace_instance`. Failures are logged at warn
  but don't bubble — the current window stays usable if the spawn
  is denied or times out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check / test --lib
  (112) clean.
- Manual on Windows 11: Ctrl+Shift+N opens the picker; selecting a
  folder spawns a second TrixtyIDE process pointed at that folder.
  Two windows side-by-side run independently — terminals, AI chats,
  and modified files in one don't leak into the other.

* Sentry telemetry (#292)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

* ci: add GitHub Actions workflow to automate Tauri build testing on Windows

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* fix(desktop): avoid Tauri state TypeId collision between Ollama and Cloud streams (#301)

`OllamaStreams` and `CloudStreams` were both type aliases of
`Arc<Mutex<HashMap<String, oneshot::Sender<()>>>>`. Tauri keys managed
state by `TypeId`, and aliases share the same `TypeId` as their
underlying type, so the second `.manage()` call panicked at startup
with "state for type ... is already being managed".

Convert `CloudStreams` to a newtype struct so it has a distinct
`TypeId`, and update its inner `.lock()` call sites accordingly.

* fix(desktop): use crypto.getRandomValues fallback for WINDOW_SESSION_ID (#302)

The runtime fallback used Math.random, which CodeQL flags as
js/insecure-randomness (CWE-338). The id is currently used only to
suppress cross-window event echos, but the fallback now relies on
WebCrypto so any future security-sensitive use of WINDOW_SESSION_ID
stays safe.

Order of preference:
  1. crypto.randomUUID         — modern Tauri webview, Node 19+, browsers
  2. crypto.getRandomValues    — older jsdom / runtimes without randomUUID
  3. Date.now + performance.now — last-resort, only echo-suppression

* feat: implement core UI components, localization hooks, and agent configuration settings for the Trixty IDE desktop application.

* Improvement: Add Discord RPC

* add: Included colavorative features using discord (beta)

* feat: implement agent architecture with context providers, workspace synchronization, and Discord RPC integration

* feat: implement StatusBar and TitleBar components with integrated collaboration state and layout controls

* feat: implement CollaborationContext for Yjs-based real-time sessions with Discord RPC integration

* feat: implement CollaborationContext for Yjs-based real-time syncing and Discord RPC join requests

* feat: implement Status Bar and Tab Bar components with collaboration status, file metadata, and keyboard-navigable tab management

* feat: add real-time collaboration support via Yjs and WebRTC with status bar integration

* chore: increment version

* fix: fixed wrong version

* feat: implement backend support for workspace synchronization, Discord RPC, and dynamic update channels

* fixed lint errors in quality check

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* feat: implement cross-platform Discord Rich Presence IPC integration

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>
jmaxdev added a commit that referenced this pull request May 2, 2026
* Main (#309)

* fixed lint errors (#308)

* feat: add Sentry error tracking (#287)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Revert "feat: add Sentry error tracking (#287)" (#291)

This reverts commit c779d52.

* feat: cloud AI streaming for OpenAI / Anthropic / Gemini / OpenRouter (#293)

PR #284 shipped cloud chat as a single-shot non-streaming bridge —
every reply landed atomically once the request finished. That made the
cloud branch feel slower than Ollama, where the chat panel renders
tokens as they arrive. This wires SSE streaming through a new
`cloud_proxy_stream` Tauri command so the UX matches.

Rust
- New `cloud_proxy_stream` and `cloud_proxy_cancel` commands behind the
  existing `validate_cloud_proxy_request` policy. The command spawns a
  detached tokio task, registers a oneshot cancel handle keyed by the
  caller-supplied `streamId`, and emits `cloud-stream` events as
  `{ kind: "data" | "done" | "error", data?, error? }`.
- New `split_sse_events` parser splits the response buffer on
  `\n\n` / `\r\n\r\n`, joins multi-`data:` lines per event, and drops
  `event:` / `id:` / `retry:` / comment lines. The Rust side stays
  provider-agnostic; the per-provider JSON shape is parsed in TS.
- The `data: [DONE]` sentinel that OpenAI / OpenRouter use is collapsed
  to a structured `done` event so callers don't have to special-case it.
  Anthropic and Gemini close the connection silently on completion;
  the task synthesises a `done` event on EOF so the awaiter resolves
  uniformly.
- Total stream size capped by the same `CLOUD_PROXY_MAX_BODY` limit
  (16 MiB) that `cloud_proxy` uses, and per-request timeout uses
  `CLOUD_PROXY_TIMEOUT_SECS` (60 s). Cancellation tears the task down
  via the oneshot.
- 10 unit tests for `split_sse_events` covering single events, multi-line
  data joining, CRLF separators, partial-event buffering, the [DONE]
  sentinel, and the SSE-spec single-leading-space rule.

TypeScript
- New `streamCloudChat(req, onDelta)` mirroring `streamOllamaChat`'s
  shape: subscribes to `cloud-stream`, filters by `streamId`, parses
  the provider-specific delta, and resolves with the full text on done.
- Provider-specific delta extractors split into
  `extractStreamDelta(provider, raw)`:
  - OpenAI / OpenRouter: `choices[0].delta.content`
  - Anthropic: `content_block_delta` events with `delta.type === "text_delta"`
    only — `message_start`, `content_block_start`, `ping`, `message_stop`,
    `input_json_delta` (future tool deltas) are ignored
  - Gemini: `candidates[*].content.parts[*].text` joined
- The matching `extractFullResponse` is exported and unit-tested so the
  non-streaming `cloudChat` shares the same parsing surface.
- The provider URL / headers / body builder is consolidated into
  `buildProviderRequest(req, stream)`. Gemini's URL flips between
  `:generateContent` and `:streamGenerateContent?alt=sse`; the others
  just toggle `stream` in the body.
- 12 unit tests covering each provider's streaming and full-response
  branches, including the role-only chunk that OpenAI sends as the
  first delta and Anthropic's housekeeping events.

UI wiring
- `AiChatComponent` cloud branch now uses `streamCloudChat` with the
  same `placeholderPushed` / `appendToLastAiMessage` pattern the Ollama
  branch uses. If the stream fails before any content arrives we still
  render a clean error bubble; if it fails mid-stream we append the
  error inline so the user sees how far the model got.
- Tools / agent mode for cloud providers stays out of scope. Each
  provider has its own tool-call shape (`tool_calls` for OpenAI,
  `tool_use` blocks for Anthropic, `functionDeclarations` for Gemini)
  and that lands in the next PR.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 (was 113 — added 12 provider tests).
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 (was 102 — added 10 SSE-parser tests).

* feat: store cloud AI provider keys in OS keychain (#294)

PR #284 shipped multi-provider keys behind a master switch and PR #286
added a banner warning that those keys lived in `settings.json`
plaintext. This wires the OS native secret store (review item I1) so
the keys never touch disk in clear text.

Backing store
- macOS: Keychain
- Windows: Credential Manager
- Linux: Secret Service / kwallet

The `keyring` crate (3.x) handles per-platform shimming. Pinned to 3.x
because keyring 4.x pulls `turso` (an embedded SQLite engine) and
`bon-macros` as transitive deps for its DB-backed unified store, none
of which we use; our `Entry::new` flow only needs the native OS
keychain backends that 3.x exposes by default.

Rust
- Four new commands behind a hard-coded provider allow-list
  (`openai` / `anthropic` / `gemini` / `openrouter`):
  `set_provider_secret`, `get_provider_secret`, `clear_provider_secret`,
  `has_provider_secret`. Service name is fixed at `trixty.ide` so
  entries are namespaced to the app and not visible to other tooling.
- `validate_secret_provider` rejects any string outside the allow-list
  so a renderer XSS can't probe arbitrary keychain entries.
- `keyring::Error::NoEntry` is mapped to `Ok(None)` / `Ok(false)` /
  `Ok(())` (idempotent clear), so the UI's "never configured" /
  "remove" paths don't surface false-positive error toasts.

TypeScript
- New `src/api/providerSecrets.ts` thin wrapper exposing
  `setProviderSecret` / `getProviderSecret` / `hasProviderSecret` /
  `clearProviderSecret` plus the `SECRET_PROVIDERS` list. Empty
  strings round-trip as "no key" — `setProviderSecret(p, "")` clears
  the entry, mirroring the way the old plaintext field treated `""`.
- Tauri-binding map extended with the four commands.

UI
- `ProviderKeysPanel` now loads each provider's key from the keychain
  on mount, holds it in component state for the reveal toggle and
  edit flow, and persists changes via a 500 ms debounced
  `setProviderSecret` (also flushed on blur). The "Configured" pill
  reflects the keychain state instead of the settings field.
- Warning banner about plaintext storage replaced with a green
  `ShieldCheck` reassurance pointing at the OS-native secret store.
- `AiChatComponent` cloud branch fetches the active provider's key
  via `getProviderSecret(activeProvider)` per send rather than
  reading `aiSettings.providerKeys`. Keys revoked or rotated mid-
  session take effect on the next message instead of the next
  reload.
- `keyForProvider` and the `ProviderKeys` import are removed from
  `client.ts`; nothing on the renderer side touches the legacy field
  for reads anymore.

Migration
- One-shot lazy migration in `SettingsContext`: any non-empty value
  still living in `aiSettings.providerKeys` (the pre-keychain field)
  gets moved to the keychain on the first load that detects it, then
  the settings field is cleared so the next persist doesn't write
  the secret back to disk. Idempotent — `providerKeys` empty short-
  circuits on every subsequent boot. Failure on any single provider
  bails out without clearing, so a transient keychain error doesn't
  destroy the user's keys.

Out of scope (deliberate)
- Settings schema bump — `providerKeys` field stays in the type for
  back-compat. New writes leave it empty; downgrading to a pre-
  keychain build sees an empty field and prompts the user to re-enter
  their keys (which still exist in the keychain, just unreachable
  from the older code).
- macOS Touch ID prompt suppression. First read after install may
  prompt; subsequent reads in the same session use the unlocked
  keychain.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 passing.
- pnpm build (Next 16 export) clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 passing.
- Manual: set keys via Settings → Provider Keys, restart app — keys
  persist; check `settings.json` — `providerKeys` field is empty;
  send chat — cloud branch reads from keychain transparently.

* feat: cloud AI tool calling and agent mode for OpenAI / Anthropic / Gemini / OpenRouter (#295)

PR #284 shipped multi-provider cloud chat as text-only. PR #293 added
streaming. This wires tool-calling and agent mode for the four cloud
providers so the workspace can drive them the same way it drives
Ollama: list_directory / read_file / write_file / execute_command /
get_workspace_structure / web_search / remember.

Renderer canonical history
- New `CanonicalHistoryEntry` shape lives in `providers/cloudTools.ts`
  alongside `ToolDefinition` and `UnifiedToolCall`. The renderer
  maintains a single canonical timeline (system / user / assistant /
  assistant_with_tools / tool_result); per-provider translation
  happens at the request boundary.
- `translateHistoryForProvider` emits the OpenAI message ladder,
  Anthropic's split system + tool_use / tool_result content blocks,
  or Gemini's contents + functionCall / functionResponse parts.
- `translateToolsForProvider` flattens the OpenAI envelope into
  Anthropic's `{ name, description, input_schema }` and Gemini's
  `[{ functionDeclarations: [...] }]`. OpenAI / OpenRouter pass
  through unchanged.
- `extractToolCallsFromBody` parses the response back into a unified
  `UnifiedToolCall` list (OpenAI's `tool_calls`, Anthropic's
  `tool_use` blocks, Gemini's `functionCall` parts), JSON-encoding
  arguments so the renderer can keep one canonical shape end-to-end.

Per-provider request
- New `cloudAgentChat(req)` in `providers/client.ts` — single-shot
  per turn. Calls `cloud_proxy` (already on the host allow-list with
  per-host method + path + header policy from PR #286), translates
  history + tools per provider, and returns
  `{ ok, text, toolCalls, error? }`.
- Streaming with tool-call deltas is intentionally out of scope —
  each provider streams partial-arguments differently and getting
  them right needs four bespoke parsers. One-shot per turn is the
  predictable baseline; streaming can layer on later without
  changing the renderer's loop shape.

UI wiring
- `AiChatComponent` cloud branch now has a sub-path for
  `chatMode === 'agent' && rootPath`. Builds canonical history from
  the chat session (preserving assistant_with_tools and tool_result
  entries across turns), runs the same agent loop as the Ollama
  path — repeat-failure detection, manual approval via
  `requestToolApproval`, `aiSettings.alwaysAllowTools`,
  `MAX_ITERATIONS = aiSettings.deepMode ? 15 : 5`, planner-mode
  gating left intact for the Ollama path. Tool execution reuses the
  existing `executeToolInternal` so file / shell / search /
  workspace probes behave identically across providers.
- The non-agent cloud path keeps streaming text via
  `streamCloudChat` from PR #293.

Tests
- 17 unit tests for `cloudTools.ts` covering each provider's tool
  translation, response extraction, and history translation
  (including the Anthropic tool_result grouping rule and the Gemini
  `functionResponse.response` object-wrapping fallback).

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 (was 125 — added 17 cloudTools tests).
- pnpm build (Next 16 export) clean.
- cargo build / clippy / fmt --check clean. cargo test --lib: 112 / 112.

* feat: detach bottom panel into a floating window (#296)

PR #282 added detach for right-panel views. Bottom panel was the next
deferred item. Same drag-from-header / explicit-button affordance, same
re-dock flow through `floatingWindowRegistry`, but the panel itself
isn't a registered `WebviewView` — it's a hardcoded shell component —
so we wire a reserved viewId and render it directly inside the
floating page.

Registry
- `DetachablePanel` now accepts `"bottom"` alongside `"right"` / `"left"`.
- New `BOTTOM_PANEL_VIEW_ID` constant (`trixty.builtin.bottom-panel`)
  exported from the registry. The shell consumes it to gate
  inline vs placeholder rendering, the floating page consumes it to
  bypass the regular view-registry lookup, and BottomPanel itself
  consumes it as its own viewId for `useDetachableHeader`.

Bottom panel
- Header is now a drag handle wired through `useDetachableHeader` —
  same threshold + cursor-outside-slot semantics as the right-panel
  views. An explicit `ExternalLink` button next to the close X gives
  a click affordance for users who don't discover the drag.
- New `isFloating` prop (default `false`). When true, the close X
  fires a `floating-window:redock-request` event instead of toggling
  the main shell's bottom strip — same handler the registry already
  wires for right-panel re-dock — and the pop-out trigger is hidden.
- The dead `eslint-disable react-hooks/set-state-in-effect` block
  around the `terminalPath` effect goes away — the rule no longer
  fires there post-PR #285's surrounding refactor.

Floating page
- `viewId === BOTTOM_PANEL_VIEW_ID` short-circuits the regular
  `useRegisteredView` path and renders `<BottomPanel isFloating />`
  inside the same `FloatingTitleBar` shell the other views use.
  Title resolves from `panel.bottom.terminal_tabs`; icon stays
  consistent with the inline header.

Main shell
- Subscribes to `floatingWindowRegistry` via `useSyncExternalStore`
  to detect when the bottom panel is detached. The
  `<ResizablePanel id="bottom">` slot stays mounted (so the layout
  preset / resize history doesn't change shape mid-detach) and we
  swap its body to a `BottomPanelDetachedPlaceholder`. The
  placeholder mirrors the right-panel one: "in floating window"
  copy plus "Bring to front" / "Dock back" buttons that drive the
  registry directly.

Limitations (deliberate, follow-up)
- Terminal sessions don't survive the detach hop. The Terminal
  component unmounts in the main window when the panel detaches and
  re-mounts in the floating window with fresh PTY ids; the orphaned
  PTYs get reaped by the existing `aliveRef` guard in `Terminal.tsx`.
  Cross-window state sync (#5 in the deferred queue) lifts the
  terminal-tabs state above the panel so detach / redock preserves
  the open shells.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build (Next 16 export): /, /_not-found, /floating prerender clean.
- Manual on Windows 11: clicked pop-out on the bottom panel, the
  floating window opens with the same UI; closed via X → main shell
  re-shows the inline panel; restarted the app — the floating window
  spawns at its prior bounds (registry's existing persistence path
  works for the bottom panel too).

* feat: cross-window chat history sync between main and floating windows (#297)

Each Tauri WebviewWindow runs its own JS realm, so detaching the AI
chat panel into a floating window today gave you two independent
\`ChatContext\` instances — sending a message in the float left the
main shell stuck on the prior conversation, and switching sessions
in either window had no effect on the other. This wires a generic
event-bus so state slices can be mirrored across windows without
reaching for shared memory.

Sync layer
- New \`src/api/crossWindowSync.ts\` exposes a tiny pub-sub layer over
  Tauri events. \`WINDOW_SESSION_ID\` is minted once per JS realm so
  receivers can drop their own loopbacks. \`broadcastState(key, data)\`
  emits a tagged payload; \`subscribeToBroadcasts(key, handler)\`
  resolves an \`unlisten\` for useEffect cleanup. Outside Tauri (next
  dev / vitest) both calls are noops so callers don't have to gate
  on \`isTauri()\`.

Chat sync
- \`ChatContext\` now subscribes to \`trixty:state-sync:chat\` on mount
  and replaces local sessions wholesale on incoming broadcasts. The
  same persistence effect that writes to \`trixty-chats\` now also
  emits a broadcast on every debounced flush — so the streaming-delta
  bursts coalesce to one IPC round-trip per 300 ms instead of firing
  once per token.
- \`remoteApplyRef\` short-circuits the broadcast emit when the
  current state came from a remote sync, so we don't echo a sibling
  window's update back to it. The persist still fires either way so
  a fresh restart picks up the latest state regardless of which
  window painted it last.

Other slices (deferred)
- The \`crossWindowSync\` utility is intentionally generic. Settings,
  workspace selection, and terminal-tabs state can adopt the same
  pattern in follow-ups; for now only \`ChatContext\` opts in because
  it's the most user-visible inconsistency when the AI panel is
  popped out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: detached the AI chat panel, sent a message in
  the float — the main shell's chat list updated within one debounce
  tick. Switched sessions in main → float caught up on next tick.
  Re-docked → no duplicate or stale messages remain.

* feat: JSON Crack-style graph view for .json files (#298)

PR #285 shipped Tree and Form visual surfaces for JSON / package.json
files. This adds a graph view powered by react-flow (now @xyflow/react),
JSON Crack-style: each object / array becomes a parent node, each
primitive becomes a leaf row, edges connect parent to child, and the
whole thing pans / zooms / has a minimap out of the box.

Visual surface
- New `JsonGraphEditor.tsx` walks the parsed JSON recursively to build
  react-flow nodes + edges. Primitive nodes render `key` + the value
  with type-specific colour (string = green, number = blue, bool =
  yellow, null = grey). Container nodes render `key` + a `[ N items ]`
  / `{ N keys }` summary. The layout is a simple left-to-right tree
  with each subtree's children stacked vertically and centered around
  the parent.
- 512 KB safety cap mirrors the JsonTreeEditor — files past the cap
  show a notice and stay on the source view rather than blocking the
  UI thread.
- Read-only by design. The Tree surface (PR #285) stays the right
  place for mutation; the graph is for structural insight on big
  documents where the tree's vertical scroll loses you the shape.

Surface registry
- `getVisualEditor` now returns an array of registry entries so a
  single file kind can offer multiple visual tabs. Generic `.json`
  registers Tree + Graph; `package.json` registers Form + Graph;
  `.env` keeps Table only. Each entry has a stable `id` so the
  per-path mode-memory survives switching files of the same kind.
- `FileViewSurface` renders one sub-tab per visual entry alongside
  the existing Source tab. Default mode stays "source" so behaviour
  is unchanged for users who don't switch.

Dependencies
- `@xyflow/react ^12.10.2` added to `apps/desktop/package.json`.
  Same package family that powers JSON Crack, n8n, Reactflow.dev's
  own demos. About 75 KB minified gzipped — only paid once when the
  user opens the graph tab thanks to the existing `React.lazy`
  registration in `getVisualEditor`.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean — the lazy
  import keeps `@xyflow/react` off the boot graph for users who never
  open a JSON file.

* feat: drag-to-reorder rows in .env and package.json visual editors (#299)

Issue #264 shipped tabular surfaces for .env and package.json. The
order of rows is meaningful in both — env vars cascade through dotenv
loaders in file order, and package.json scripts / deps are usually
ordered to make the file readable. Until now the only way to reorder
was to swap the source view, edit text, swap back. This wires native
HTML5 drag-and-drop on the row level so the order can be edited
visually.

Hook
- New `src/hooks/useDragReorder.ts` exposes a generic
  `useDragReorder({ items, getId, onReorder, groupKey? })` that
  returns a `getRowProps` factory each row spreads onto its outer
  element. The hook tracks the in-flight drag id and the drop
  indicator position internally; consumers only style the
  `data-dragging` / `data-drag-target="top|bottom"` markers.
- Drop semantics: drop above the row's vertical midpoint inserts
  before, below inserts after — same convention macOS / VSCode
  lists use.
- Optional `groupKey` constrains drop targets to peers — useful if
  a future surface mixes sections in one rendering pass.

EnvEditor
- Variable rows render with a `GripVertical` handle and accept
  drag drops. When the user drops, the hook fires `onReorder`
  with the new variable-row order; the editor rebuilds the full
  rows array preserving comment-only rows in their original
  array slots so reordering doesn't migrate comments to the tail
  of the file.

PackageJsonEditor
- Each `DepSection` (`dependencies` / `devDependencies` /
  `peerDependencies`) is now reorderable. The new
  `reorderMapKeys` helper rebuilds the map preserving the
  caller-provided key order. JS keeps insertion order for string
  keys, so `JSON.stringify` writes the new order to disk
  exactly. Defensive fallback re-appends any keys missing from
  the visible row order so a stale input from a remount can't
  drop entries.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: opened `apps/desktop/.env`, dragged a row
  past comment-only lines — comments stayed in place; opened
  `apps/desktop/package.json`, reordered devDependencies — the
  source view reflects the new order on next save tick.

* feat: open another workspace in a new window via Ctrl+Shift+N (#300)

Two repos side-by-side without context-switching. Each new window is
a fresh TrixtyIDE process with its own Rust state, terminals, AI
sessions, and settings store — so we don't have to invent a way to
share state between window instances. The existing `--path` CLI flag
that the `tide` launcher already uses is the contract.

Rust
- New `spawn_workspace_instance(path)` Tauri command resolves
  `current_exe()`, canonicalises the user-supplied folder, and
  spawns a detached `<binary> --path <folder>` process. Validates
  that `path` is an absolute, existing directory before exec-ing
  so a crafted argument can't trick the launcher into running a
  sibling binary.
- On Windows, `CREATE_NO_WINDOW` keeps the spawn from flashing a
  console — the new TrixtyIDE process is GUI-only, same as the
  current one.

UI
- New `Ctrl+Shift+N` shortcut in the main shell. Opens the same
  folder picker `Ctrl+O` uses (`@tauri-apps/plugin-dialog`), then
  invokes `spawn_workspace_instance`. Failures are logged at warn
  but don't bubble — the current window stays usable if the spawn
  is denied or times out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check / test --lib
  (112) clean.
- Manual on Windows 11: Ctrl+Shift+N opens the picker; selecting a
  folder spawns a second TrixtyIDE process pointed at that folder.
  Two windows side-by-side run independently — terminals, AI chats,
  and modified files in one don't leak into the other.

* Sentry telemetry (#292)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

* ci: add GitHub Actions workflow to automate Tauri build testing on Windows

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* fix(desktop): avoid Tauri state TypeId collision between Ollama and Cloud streams (#301)

`OllamaStreams` and `CloudStreams` were both type aliases of
`Arc<Mutex<HashMap<String, oneshot::Sender<()>>>>`. Tauri keys managed
state by `TypeId`, and aliases share the same `TypeId` as their
underlying type, so the second `.manage()` call panicked at startup
with "state for type ... is already being managed".

Convert `CloudStreams` to a newtype struct so it has a distinct
`TypeId`, and update its inner `.lock()` call sites accordingly.

* fix(desktop): use crypto.getRandomValues fallback for WINDOW_SESSION_ID (#302)

The runtime fallback used Math.random, which CodeQL flags as
js/insecure-randomness (CWE-338). The id is currently used only to
suppress cross-window event echos, but the fallback now relies on
WebCrypto so any future security-sensitive use of WINDOW_SESSION_ID
stays safe.

Order of preference:
  1. crypto.randomUUID         — modern Tauri webview, Node 19+, browsers
  2. crypto.getRandomValues    — older jsdom / runtimes without randomUUID
  3. Date.now + performance.now — last-resort, only echo-suppression

* feat: implement core UI components, localization hooks, and agent configuration settings for the Trixty IDE desktop application.

* Improvement: Add Discord RPC

* add: Included colavorative features using discord (beta)

* feat: implement agent architecture with context providers, workspace synchronization, and Discord RPC integration

* feat: implement StatusBar and TitleBar components with integrated collaboration state and layout controls

* feat: implement CollaborationContext for Yjs-based real-time sessions with Discord RPC integration

* feat: implement CollaborationContext for Yjs-based real-time syncing and Discord RPC join requests

* feat: implement Status Bar and Tab Bar components with collaboration status, file metadata, and keyboard-navigable tab management

* feat: add real-time collaboration support via Yjs and WebRTC with status bar integration

* chore: increment version

* fix: fixed wrong version

* feat: implement backend support for workspace synchronization, Discord RPC, and dynamic update channels

* fixed lint errors in quality check

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* feat: implement cross-platform Discord Rich Presence IPC integration

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* feat: integrate a internal browser for localhost (#310)

* feat: integrate a internal browser for localhost

* Potential fix for pull request finding 'CodeQL / DOM text reinterpreted as HTML'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Potential fix for pull request finding 'CodeQL / DOM text reinterpreted as HTML'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* feat: initialize Tauri desktop application configuration and dependency manifest

* feat: implement BrowserView component with configurable port connectivity and status monitoring

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
jmaxdev added a commit that referenced this pull request May 2, 2026
* Main (#309)

* fixed lint errors (#308)

* feat: add Sentry error tracking (#287)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Revert "feat: add Sentry error tracking (#287)" (#291)

This reverts commit c779d52.

* feat: cloud AI streaming for OpenAI / Anthropic / Gemini / OpenRouter (#293)

PR #284 shipped cloud chat as a single-shot non-streaming bridge —
every reply landed atomically once the request finished. That made the
cloud branch feel slower than Ollama, where the chat panel renders
tokens as they arrive. This wires SSE streaming through a new
`cloud_proxy_stream` Tauri command so the UX matches.

Rust
- New `cloud_proxy_stream` and `cloud_proxy_cancel` commands behind the
  existing `validate_cloud_proxy_request` policy. The command spawns a
  detached tokio task, registers a oneshot cancel handle keyed by the
  caller-supplied `streamId`, and emits `cloud-stream` events as
  `{ kind: "data" | "done" | "error", data?, error? }`.
- New `split_sse_events` parser splits the response buffer on
  `\n\n` / `\r\n\r\n`, joins multi-`data:` lines per event, and drops
  `event:` / `id:` / `retry:` / comment lines. The Rust side stays
  provider-agnostic; the per-provider JSON shape is parsed in TS.
- The `data: [DONE]` sentinel that OpenAI / OpenRouter use is collapsed
  to a structured `done` event so callers don't have to special-case it.
  Anthropic and Gemini close the connection silently on completion;
  the task synthesises a `done` event on EOF so the awaiter resolves
  uniformly.
- Total stream size capped by the same `CLOUD_PROXY_MAX_BODY` limit
  (16 MiB) that `cloud_proxy` uses, and per-request timeout uses
  `CLOUD_PROXY_TIMEOUT_SECS` (60 s). Cancellation tears the task down
  via the oneshot.
- 10 unit tests for `split_sse_events` covering single events, multi-line
  data joining, CRLF separators, partial-event buffering, the [DONE]
  sentinel, and the SSE-spec single-leading-space rule.

TypeScript
- New `streamCloudChat(req, onDelta)` mirroring `streamOllamaChat`'s
  shape: subscribes to `cloud-stream`, filters by `streamId`, parses
  the provider-specific delta, and resolves with the full text on done.
- Provider-specific delta extractors split into
  `extractStreamDelta(provider, raw)`:
  - OpenAI / OpenRouter: `choices[0].delta.content`
  - Anthropic: `content_block_delta` events with `delta.type === "text_delta"`
    only — `message_start`, `content_block_start`, `ping`, `message_stop`,
    `input_json_delta` (future tool deltas) are ignored
  - Gemini: `candidates[*].content.parts[*].text` joined
- The matching `extractFullResponse` is exported and unit-tested so the
  non-streaming `cloudChat` shares the same parsing surface.
- The provider URL / headers / body builder is consolidated into
  `buildProviderRequest(req, stream)`. Gemini's URL flips between
  `:generateContent` and `:streamGenerateContent?alt=sse`; the others
  just toggle `stream` in the body.
- 12 unit tests covering each provider's streaming and full-response
  branches, including the role-only chunk that OpenAI sends as the
  first delta and Anthropic's housekeeping events.

UI wiring
- `AiChatComponent` cloud branch now uses `streamCloudChat` with the
  same `placeholderPushed` / `appendToLastAiMessage` pattern the Ollama
  branch uses. If the stream fails before any content arrives we still
  render a clean error bubble; if it fails mid-stream we append the
  error inline so the user sees how far the model got.
- Tools / agent mode for cloud providers stays out of scope. Each
  provider has its own tool-call shape (`tool_calls` for OpenAI,
  `tool_use` blocks for Anthropic, `functionDeclarations` for Gemini)
  and that lands in the next PR.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 (was 113 — added 12 provider tests).
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 (was 102 — added 10 SSE-parser tests).

* feat: store cloud AI provider keys in OS keychain (#294)

PR #284 shipped multi-provider keys behind a master switch and PR #286
added a banner warning that those keys lived in `settings.json`
plaintext. This wires the OS native secret store (review item I1) so
the keys never touch disk in clear text.

Backing store
- macOS: Keychain
- Windows: Credential Manager
- Linux: Secret Service / kwallet

The `keyring` crate (3.x) handles per-platform shimming. Pinned to 3.x
because keyring 4.x pulls `turso` (an embedded SQLite engine) and
`bon-macros` as transitive deps for its DB-backed unified store, none
of which we use; our `Entry::new` flow only needs the native OS
keychain backends that 3.x exposes by default.

Rust
- Four new commands behind a hard-coded provider allow-list
  (`openai` / `anthropic` / `gemini` / `openrouter`):
  `set_provider_secret`, `get_provider_secret`, `clear_provider_secret`,
  `has_provider_secret`. Service name is fixed at `trixty.ide` so
  entries are namespaced to the app and not visible to other tooling.
- `validate_secret_provider` rejects any string outside the allow-list
  so a renderer XSS can't probe arbitrary keychain entries.
- `keyring::Error::NoEntry` is mapped to `Ok(None)` / `Ok(false)` /
  `Ok(())` (idempotent clear), so the UI's "never configured" /
  "remove" paths don't surface false-positive error toasts.

TypeScript
- New `src/api/providerSecrets.ts` thin wrapper exposing
  `setProviderSecret` / `getProviderSecret` / `hasProviderSecret` /
  `clearProviderSecret` plus the `SECRET_PROVIDERS` list. Empty
  strings round-trip as "no key" — `setProviderSecret(p, "")` clears
  the entry, mirroring the way the old plaintext field treated `""`.
- Tauri-binding map extended with the four commands.

UI
- `ProviderKeysPanel` now loads each provider's key from the keychain
  on mount, holds it in component state for the reveal toggle and
  edit flow, and persists changes via a 500 ms debounced
  `setProviderSecret` (also flushed on blur). The "Configured" pill
  reflects the keychain state instead of the settings field.
- Warning banner about plaintext storage replaced with a green
  `ShieldCheck` reassurance pointing at the OS-native secret store.
- `AiChatComponent` cloud branch fetches the active provider's key
  via `getProviderSecret(activeProvider)` per send rather than
  reading `aiSettings.providerKeys`. Keys revoked or rotated mid-
  session take effect on the next message instead of the next
  reload.
- `keyForProvider` and the `ProviderKeys` import are removed from
  `client.ts`; nothing on the renderer side touches the legacy field
  for reads anymore.

Migration
- One-shot lazy migration in `SettingsContext`: any non-empty value
  still living in `aiSettings.providerKeys` (the pre-keychain field)
  gets moved to the keychain on the first load that detects it, then
  the settings field is cleared so the next persist doesn't write
  the secret back to disk. Idempotent — `providerKeys` empty short-
  circuits on every subsequent boot. Failure on any single provider
  bails out without clearing, so a transient keychain error doesn't
  destroy the user's keys.

Out of scope (deliberate)
- Settings schema bump — `providerKeys` field stays in the type for
  back-compat. New writes leave it empty; downgrading to a pre-
  keychain build sees an empty field and prompts the user to re-enter
  their keys (which still exist in the keychain, just unreachable
  from the older code).
- macOS Touch ID prompt suppression. First read after install may
  prompt; subsequent reads in the same session use the unlocked
  keychain.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 passing.
- pnpm build (Next 16 export) clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 passing.
- Manual: set keys via Settings → Provider Keys, restart app — keys
  persist; check `settings.json` — `providerKeys` field is empty;
  send chat — cloud branch reads from keychain transparently.

* feat: cloud AI tool calling and agent mode for OpenAI / Anthropic / Gemini / OpenRouter (#295)

PR #284 shipped multi-provider cloud chat as text-only. PR #293 added
streaming. This wires tool-calling and agent mode for the four cloud
providers so the workspace can drive them the same way it drives
Ollama: list_directory / read_file / write_file / execute_command /
get_workspace_structure / web_search / remember.

Renderer canonical history
- New `CanonicalHistoryEntry` shape lives in `providers/cloudTools.ts`
  alongside `ToolDefinition` and `UnifiedToolCall`. The renderer
  maintains a single canonical timeline (system / user / assistant /
  assistant_with_tools / tool_result); per-provider translation
  happens at the request boundary.
- `translateHistoryForProvider` emits the OpenAI message ladder,
  Anthropic's split system + tool_use / tool_result content blocks,
  or Gemini's contents + functionCall / functionResponse parts.
- `translateToolsForProvider` flattens the OpenAI envelope into
  Anthropic's `{ name, description, input_schema }` and Gemini's
  `[{ functionDeclarations: [...] }]`. OpenAI / OpenRouter pass
  through unchanged.
- `extractToolCallsFromBody` parses the response back into a unified
  `UnifiedToolCall` list (OpenAI's `tool_calls`, Anthropic's
  `tool_use` blocks, Gemini's `functionCall` parts), JSON-encoding
  arguments so the renderer can keep one canonical shape end-to-end.

Per-provider request
- New `cloudAgentChat(req)` in `providers/client.ts` — single-shot
  per turn. Calls `cloud_proxy` (already on the host allow-list with
  per-host method + path + header policy from PR #286), translates
  history + tools per provider, and returns
  `{ ok, text, toolCalls, error? }`.
- Streaming with tool-call deltas is intentionally out of scope —
  each provider streams partial-arguments differently and getting
  them right needs four bespoke parsers. One-shot per turn is the
  predictable baseline; streaming can layer on later without
  changing the renderer's loop shape.

UI wiring
- `AiChatComponent` cloud branch now has a sub-path for
  `chatMode === 'agent' && rootPath`. Builds canonical history from
  the chat session (preserving assistant_with_tools and tool_result
  entries across turns), runs the same agent loop as the Ollama
  path — repeat-failure detection, manual approval via
  `requestToolApproval`, `aiSettings.alwaysAllowTools`,
  `MAX_ITERATIONS = aiSettings.deepMode ? 15 : 5`, planner-mode
  gating left intact for the Ollama path. Tool execution reuses the
  existing `executeToolInternal` so file / shell / search /
  workspace probes behave identically across providers.
- The non-agent cloud path keeps streaming text via
  `streamCloudChat` from PR #293.

Tests
- 17 unit tests for `cloudTools.ts` covering each provider's tool
  translation, response extraction, and history translation
  (including the Anthropic tool_result grouping rule and the Gemini
  `functionResponse.response` object-wrapping fallback).

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 (was 125 — added 17 cloudTools tests).
- pnpm build (Next 16 export) clean.
- cargo build / clippy / fmt --check clean. cargo test --lib: 112 / 112.

* feat: detach bottom panel into a floating window (#296)

PR #282 added detach for right-panel views. Bottom panel was the next
deferred item. Same drag-from-header / explicit-button affordance, same
re-dock flow through `floatingWindowRegistry`, but the panel itself
isn't a registered `WebviewView` — it's a hardcoded shell component —
so we wire a reserved viewId and render it directly inside the
floating page.

Registry
- `DetachablePanel` now accepts `"bottom"` alongside `"right"` / `"left"`.
- New `BOTTOM_PANEL_VIEW_ID` constant (`trixty.builtin.bottom-panel`)
  exported from the registry. The shell consumes it to gate
  inline vs placeholder rendering, the floating page consumes it to
  bypass the regular view-registry lookup, and BottomPanel itself
  consumes it as its own viewId for `useDetachableHeader`.

Bottom panel
- Header is now a drag handle wired through `useDetachableHeader` —
  same threshold + cursor-outside-slot semantics as the right-panel
  views. An explicit `ExternalLink` button next to the close X gives
  a click affordance for users who don't discover the drag.
- New `isFloating` prop (default `false`). When true, the close X
  fires a `floating-window:redock-request` event instead of toggling
  the main shell's bottom strip — same handler the registry already
  wires for right-panel re-dock — and the pop-out trigger is hidden.
- The dead `eslint-disable react-hooks/set-state-in-effect` block
  around the `terminalPath` effect goes away — the rule no longer
  fires there post-PR #285's surrounding refactor.

Floating page
- `viewId === BOTTOM_PANEL_VIEW_ID` short-circuits the regular
  `useRegisteredView` path and renders `<BottomPanel isFloating />`
  inside the same `FloatingTitleBar` shell the other views use.
  Title resolves from `panel.bottom.terminal_tabs`; icon stays
  consistent with the inline header.

Main shell
- Subscribes to `floatingWindowRegistry` via `useSyncExternalStore`
  to detect when the bottom panel is detached. The
  `<ResizablePanel id="bottom">` slot stays mounted (so the layout
  preset / resize history doesn't change shape mid-detach) and we
  swap its body to a `BottomPanelDetachedPlaceholder`. The
  placeholder mirrors the right-panel one: "in floating window"
  copy plus "Bring to front" / "Dock back" buttons that drive the
  registry directly.

Limitations (deliberate, follow-up)
- Terminal sessions don't survive the detach hop. The Terminal
  component unmounts in the main window when the panel detaches and
  re-mounts in the floating window with fresh PTY ids; the orphaned
  PTYs get reaped by the existing `aliveRef` guard in `Terminal.tsx`.
  Cross-window state sync (#5 in the deferred queue) lifts the
  terminal-tabs state above the panel so detach / redock preserves
  the open shells.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build (Next 16 export): /, /_not-found, /floating prerender clean.
- Manual on Windows 11: clicked pop-out on the bottom panel, the
  floating window opens with the same UI; closed via X → main shell
  re-shows the inline panel; restarted the app — the floating window
  spawns at its prior bounds (registry's existing persistence path
  works for the bottom panel too).

* feat: cross-window chat history sync between main and floating windows (#297)

Each Tauri WebviewWindow runs its own JS realm, so detaching the AI
chat panel into a floating window today gave you two independent
\`ChatContext\` instances — sending a message in the float left the
main shell stuck on the prior conversation, and switching sessions
in either window had no effect on the other. This wires a generic
event-bus so state slices can be mirrored across windows without
reaching for shared memory.

Sync layer
- New \`src/api/crossWindowSync.ts\` exposes a tiny pub-sub layer over
  Tauri events. \`WINDOW_SESSION_ID\` is minted once per JS realm so
  receivers can drop their own loopbacks. \`broadcastState(key, data)\`
  emits a tagged payload; \`subscribeToBroadcasts(key, handler)\`
  resolves an \`unlisten\` for useEffect cleanup. Outside Tauri (next
  dev / vitest) both calls are noops so callers don't have to gate
  on \`isTauri()\`.

Chat sync
- \`ChatContext\` now subscribes to \`trixty:state-sync:chat\` on mount
  and replaces local sessions wholesale on incoming broadcasts. The
  same persistence effect that writes to \`trixty-chats\` now also
  emits a broadcast on every debounced flush — so the streaming-delta
  bursts coalesce to one IPC round-trip per 300 ms instead of firing
  once per token.
- \`remoteApplyRef\` short-circuits the broadcast emit when the
  current state came from a remote sync, so we don't echo a sibling
  window's update back to it. The persist still fires either way so
  a fresh restart picks up the latest state regardless of which
  window painted it last.

Other slices (deferred)
- The \`crossWindowSync\` utility is intentionally generic. Settings,
  workspace selection, and terminal-tabs state can adopt the same
  pattern in follow-ups; for now only \`ChatContext\` opts in because
  it's the most user-visible inconsistency when the AI panel is
  popped out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: detached the AI chat panel, sent a message in
  the float — the main shell's chat list updated within one debounce
  tick. Switched sessions in main → float caught up on next tick.
  Re-docked → no duplicate or stale messages remain.

* feat: JSON Crack-style graph view for .json files (#298)

PR #285 shipped Tree and Form visual surfaces for JSON / package.json
files. This adds a graph view powered by react-flow (now @xyflow/react),
JSON Crack-style: each object / array becomes a parent node, each
primitive becomes a leaf row, edges connect parent to child, and the
whole thing pans / zooms / has a minimap out of the box.

Visual surface
- New `JsonGraphEditor.tsx` walks the parsed JSON recursively to build
  react-flow nodes + edges. Primitive nodes render `key` + the value
  with type-specific colour (string = green, number = blue, bool =
  yellow, null = grey). Container nodes render `key` + a `[ N items ]`
  / `{ N keys }` summary. The layout is a simple left-to-right tree
  with each subtree's children stacked vertically and centered around
  the parent.
- 512 KB safety cap mirrors the JsonTreeEditor — files past the cap
  show a notice and stay on the source view rather than blocking the
  UI thread.
- Read-only by design. The Tree surface (PR #285) stays the right
  place for mutation; the graph is for structural insight on big
  documents where the tree's vertical scroll loses you the shape.

Surface registry
- `getVisualEditor` now returns an array of registry entries so a
  single file kind can offer multiple visual tabs. Generic `.json`
  registers Tree + Graph; `package.json` registers Form + Graph;
  `.env` keeps Table only. Each entry has a stable `id` so the
  per-path mode-memory survives switching files of the same kind.
- `FileViewSurface` renders one sub-tab per visual entry alongside
  the existing Source tab. Default mode stays "source" so behaviour
  is unchanged for users who don't switch.

Dependencies
- `@xyflow/react ^12.10.2` added to `apps/desktop/package.json`.
  Same package family that powers JSON Crack, n8n, Reactflow.dev's
  own demos. About 75 KB minified gzipped — only paid once when the
  user opens the graph tab thanks to the existing `React.lazy`
  registration in `getVisualEditor`.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean — the lazy
  import keeps `@xyflow/react` off the boot graph for users who never
  open a JSON file.

* feat: drag-to-reorder rows in .env and package.json visual editors (#299)

Issue #264 shipped tabular surfaces for .env and package.json. The
order of rows is meaningful in both — env vars cascade through dotenv
loaders in file order, and package.json scripts / deps are usually
ordered to make the file readable. Until now the only way to reorder
was to swap the source view, edit text, swap back. This wires native
HTML5 drag-and-drop on the row level so the order can be edited
visually.

Hook
- New `src/hooks/useDragReorder.ts` exposes a generic
  `useDragReorder({ items, getId, onReorder, groupKey? })` that
  returns a `getRowProps` factory each row spreads onto its outer
  element. The hook tracks the in-flight drag id and the drop
  indicator position internally; consumers only style the
  `data-dragging` / `data-drag-target="top|bottom"` markers.
- Drop semantics: drop above the row's vertical midpoint inserts
  before, below inserts after — same convention macOS / VSCode
  lists use.
- Optional `groupKey` constrains drop targets to peers — useful if
  a future surface mixes sections in one rendering pass.

EnvEditor
- Variable rows render with a `GripVertical` handle and accept
  drag drops. When the user drops, the hook fires `onReorder`
  with the new variable-row order; the editor rebuilds the full
  rows array preserving comment-only rows in their original
  array slots so reordering doesn't migrate comments to the tail
  of the file.

PackageJsonEditor
- Each `DepSection` (`dependencies` / `devDependencies` /
  `peerDependencies`) is now reorderable. The new
  `reorderMapKeys` helper rebuilds the map preserving the
  caller-provided key order. JS keeps insertion order for string
  keys, so `JSON.stringify` writes the new order to disk
  exactly. Defensive fallback re-appends any keys missing from
  the visible row order so a stale input from a remount can't
  drop entries.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: opened `apps/desktop/.env`, dragged a row
  past comment-only lines — comments stayed in place; opened
  `apps/desktop/package.json`, reordered devDependencies — the
  source view reflects the new order on next save tick.

* feat: open another workspace in a new window via Ctrl+Shift+N (#300)

Two repos side-by-side without context-switching. Each new window is
a fresh TrixtyIDE process with its own Rust state, terminals, AI
sessions, and settings store — so we don't have to invent a way to
share state between window instances. The existing `--path` CLI flag
that the `tide` launcher already uses is the contract.

Rust
- New `spawn_workspace_instance(path)` Tauri command resolves
  `current_exe()`, canonicalises the user-supplied folder, and
  spawns a detached `<binary> --path <folder>` process. Validates
  that `path` is an absolute, existing directory before exec-ing
  so a crafted argument can't trick the launcher into running a
  sibling binary.
- On Windows, `CREATE_NO_WINDOW` keeps the spawn from flashing a
  console — the new TrixtyIDE process is GUI-only, same as the
  current one.

UI
- New `Ctrl+Shift+N` shortcut in the main shell. Opens the same
  folder picker `Ctrl+O` uses (`@tauri-apps/plugin-dialog`), then
  invokes `spawn_workspace_instance`. Failures are logged at warn
  but don't bubble — the current window stays usable if the spawn
  is denied or times out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check / test --lib
  (112) clean.
- Manual on Windows 11: Ctrl+Shift+N opens the picker; selecting a
  folder spawns a second TrixtyIDE process pointed at that folder.
  Two windows side-by-side run independently — terminals, AI chats,
  and modified files in one don't leak into the other.

* Sentry telemetry (#292)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

* ci: add GitHub Actions workflow to automate Tauri build testing on Windows

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* fix(desktop): avoid Tauri state TypeId collision between Ollama and Cloud streams (#301)

`OllamaStreams` and `CloudStreams` were both type aliases of
`Arc<Mutex<HashMap<String, oneshot::Sender<()>>>>`. Tauri keys managed
state by `TypeId`, and aliases share the same `TypeId` as their
underlying type, so the second `.manage()` call panicked at startup
with "state for type ... is already being managed".

Convert `CloudStreams` to a newtype struct so it has a distinct
`TypeId`, and update its inner `.lock()` call sites accordingly.

* fix(desktop): use crypto.getRandomValues fallback for WINDOW_SESSION_ID (#302)

The runtime fallback used Math.random, which CodeQL flags as
js/insecure-randomness (CWE-338). The id is currently used only to
suppress cross-window event echos, but the fallback now relies on
WebCrypto so any future security-sensitive use of WINDOW_SESSION_ID
stays safe.

Order of preference:
  1. crypto.randomUUID         — modern Tauri webview, Node 19+, browsers
  2. crypto.getRandomValues    — older jsdom / runtimes without randomUUID
  3. Date.now + performance.now — last-resort, only echo-suppression

* feat: implement core UI components, localization hooks, and agent configuration settings for the Trixty IDE desktop application.

* Improvement: Add Discord RPC

* add: Included colavorative features using discord (beta)

* feat: implement agent architecture with context providers, workspace synchronization, and Discord RPC integration

* feat: implement StatusBar and TitleBar components with integrated collaboration state and layout controls

* feat: implement CollaborationContext for Yjs-based real-time sessions with Discord RPC integration

* feat: implement CollaborationContext for Yjs-based real-time syncing and Discord RPC join requests

* feat: implement Status Bar and Tab Bar components with collaboration status, file metadata, and keyboard-navigable tab management

* feat: add real-time collaboration support via Yjs and WebRTC with status bar integration

* chore: increment version

* fix: fixed wrong version

* feat: implement backend support for workspace synchronization, Discord RPC, and dynamic update channels

* fixed lint errors in quality check

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* feat: implement cross-platform Discord Rich Presence IPC integration

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* feat: integrate a internal browser for localhost (#310)

* feat: integrate a internal browser for localhost

* Potential fix for pull request finding 'CodeQL / DOM text reinterpreted as HTML'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Potential fix for pull request finding 'CodeQL / DOM text reinterpreted as HTML'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* feat: initialize Tauri desktop application configuration and dependency manifest

* feat: implement BrowserView component with configurable port connectivity and status monitoring

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* chore(bump version): bump version

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
jmaxdev added a commit that referenced this pull request May 2, 2026
* Main (#309)

* fixed lint errors (#308)

* feat: add Sentry error tracking (#287)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Revert "feat: add Sentry error tracking (#287)" (#291)

This reverts commit c779d52.

* feat: cloud AI streaming for OpenAI / Anthropic / Gemini / OpenRouter (#293)

PR #284 shipped cloud chat as a single-shot non-streaming bridge —
every reply landed atomically once the request finished. That made the
cloud branch feel slower than Ollama, where the chat panel renders
tokens as they arrive. This wires SSE streaming through a new
`cloud_proxy_stream` Tauri command so the UX matches.

Rust
- New `cloud_proxy_stream` and `cloud_proxy_cancel` commands behind the
  existing `validate_cloud_proxy_request` policy. The command spawns a
  detached tokio task, registers a oneshot cancel handle keyed by the
  caller-supplied `streamId`, and emits `cloud-stream` events as
  `{ kind: "data" | "done" | "error", data?, error? }`.
- New `split_sse_events` parser splits the response buffer on
  `\n\n` / `\r\n\r\n`, joins multi-`data:` lines per event, and drops
  `event:` / `id:` / `retry:` / comment lines. The Rust side stays
  provider-agnostic; the per-provider JSON shape is parsed in TS.
- The `data: [DONE]` sentinel that OpenAI / OpenRouter use is collapsed
  to a structured `done` event so callers don't have to special-case it.
  Anthropic and Gemini close the connection silently on completion;
  the task synthesises a `done` event on EOF so the awaiter resolves
  uniformly.
- Total stream size capped by the same `CLOUD_PROXY_MAX_BODY` limit
  (16 MiB) that `cloud_proxy` uses, and per-request timeout uses
  `CLOUD_PROXY_TIMEOUT_SECS` (60 s). Cancellation tears the task down
  via the oneshot.
- 10 unit tests for `split_sse_events` covering single events, multi-line
  data joining, CRLF separators, partial-event buffering, the [DONE]
  sentinel, and the SSE-spec single-leading-space rule.

TypeScript
- New `streamCloudChat(req, onDelta)` mirroring `streamOllamaChat`'s
  shape: subscribes to `cloud-stream`, filters by `streamId`, parses
  the provider-specific delta, and resolves with the full text on done.
- Provider-specific delta extractors split into
  `extractStreamDelta(provider, raw)`:
  - OpenAI / OpenRouter: `choices[0].delta.content`
  - Anthropic: `content_block_delta` events with `delta.type === "text_delta"`
    only — `message_start`, `content_block_start`, `ping`, `message_stop`,
    `input_json_delta` (future tool deltas) are ignored
  - Gemini: `candidates[*].content.parts[*].text` joined
- The matching `extractFullResponse` is exported and unit-tested so the
  non-streaming `cloudChat` shares the same parsing surface.
- The provider URL / headers / body builder is consolidated into
  `buildProviderRequest(req, stream)`. Gemini's URL flips between
  `:generateContent` and `:streamGenerateContent?alt=sse`; the others
  just toggle `stream` in the body.
- 12 unit tests covering each provider's streaming and full-response
  branches, including the role-only chunk that OpenAI sends as the
  first delta and Anthropic's housekeeping events.

UI wiring
- `AiChatComponent` cloud branch now uses `streamCloudChat` with the
  same `placeholderPushed` / `appendToLastAiMessage` pattern the Ollama
  branch uses. If the stream fails before any content arrives we still
  render a clean error bubble; if it fails mid-stream we append the
  error inline so the user sees how far the model got.
- Tools / agent mode for cloud providers stays out of scope. Each
  provider has its own tool-call shape (`tool_calls` for OpenAI,
  `tool_use` blocks for Anthropic, `functionDeclarations` for Gemini)
  and that lands in the next PR.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 (was 113 — added 12 provider tests).
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 (was 102 — added 10 SSE-parser tests).

* feat: store cloud AI provider keys in OS keychain (#294)

PR #284 shipped multi-provider keys behind a master switch and PR #286
added a banner warning that those keys lived in `settings.json`
plaintext. This wires the OS native secret store (review item I1) so
the keys never touch disk in clear text.

Backing store
- macOS: Keychain
- Windows: Credential Manager
- Linux: Secret Service / kwallet

The `keyring` crate (3.x) handles per-platform shimming. Pinned to 3.x
because keyring 4.x pulls `turso` (an embedded SQLite engine) and
`bon-macros` as transitive deps for its DB-backed unified store, none
of which we use; our `Entry::new` flow only needs the native OS
keychain backends that 3.x exposes by default.

Rust
- Four new commands behind a hard-coded provider allow-list
  (`openai` / `anthropic` / `gemini` / `openrouter`):
  `set_provider_secret`, `get_provider_secret`, `clear_provider_secret`,
  `has_provider_secret`. Service name is fixed at `trixty.ide` so
  entries are namespaced to the app and not visible to other tooling.
- `validate_secret_provider` rejects any string outside the allow-list
  so a renderer XSS can't probe arbitrary keychain entries.
- `keyring::Error::NoEntry` is mapped to `Ok(None)` / `Ok(false)` /
  `Ok(())` (idempotent clear), so the UI's "never configured" /
  "remove" paths don't surface false-positive error toasts.

TypeScript
- New `src/api/providerSecrets.ts` thin wrapper exposing
  `setProviderSecret` / `getProviderSecret` / `hasProviderSecret` /
  `clearProviderSecret` plus the `SECRET_PROVIDERS` list. Empty
  strings round-trip as "no key" — `setProviderSecret(p, "")` clears
  the entry, mirroring the way the old plaintext field treated `""`.
- Tauri-binding map extended with the four commands.

UI
- `ProviderKeysPanel` now loads each provider's key from the keychain
  on mount, holds it in component state for the reveal toggle and
  edit flow, and persists changes via a 500 ms debounced
  `setProviderSecret` (also flushed on blur). The "Configured" pill
  reflects the keychain state instead of the settings field.
- Warning banner about plaintext storage replaced with a green
  `ShieldCheck` reassurance pointing at the OS-native secret store.
- `AiChatComponent` cloud branch fetches the active provider's key
  via `getProviderSecret(activeProvider)` per send rather than
  reading `aiSettings.providerKeys`. Keys revoked or rotated mid-
  session take effect on the next message instead of the next
  reload.
- `keyForProvider` and the `ProviderKeys` import are removed from
  `client.ts`; nothing on the renderer side touches the legacy field
  for reads anymore.

Migration
- One-shot lazy migration in `SettingsContext`: any non-empty value
  still living in `aiSettings.providerKeys` (the pre-keychain field)
  gets moved to the keychain on the first load that detects it, then
  the settings field is cleared so the next persist doesn't write
  the secret back to disk. Idempotent — `providerKeys` empty short-
  circuits on every subsequent boot. Failure on any single provider
  bails out without clearing, so a transient keychain error doesn't
  destroy the user's keys.

Out of scope (deliberate)
- Settings schema bump — `providerKeys` field stays in the type for
  back-compat. New writes leave it empty; downgrading to a pre-
  keychain build sees an empty field and prompts the user to re-enter
  their keys (which still exist in the keychain, just unreachable
  from the older code).
- macOS Touch ID prompt suppression. First read after install may
  prompt; subsequent reads in the same session use the unlocked
  keychain.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 passing.
- pnpm build (Next 16 export) clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 passing.
- Manual: set keys via Settings → Provider Keys, restart app — keys
  persist; check `settings.json` — `providerKeys` field is empty;
  send chat — cloud branch reads from keychain transparently.

* feat: cloud AI tool calling and agent mode for OpenAI / Anthropic / Gemini / OpenRouter (#295)

PR #284 shipped multi-provider cloud chat as text-only. PR #293 added
streaming. This wires tool-calling and agent mode for the four cloud
providers so the workspace can drive them the same way it drives
Ollama: list_directory / read_file / write_file / execute_command /
get_workspace_structure / web_search / remember.

Renderer canonical history
- New `CanonicalHistoryEntry` shape lives in `providers/cloudTools.ts`
  alongside `ToolDefinition` and `UnifiedToolCall`. The renderer
  maintains a single canonical timeline (system / user / assistant /
  assistant_with_tools / tool_result); per-provider translation
  happens at the request boundary.
- `translateHistoryForProvider` emits the OpenAI message ladder,
  Anthropic's split system + tool_use / tool_result content blocks,
  or Gemini's contents + functionCall / functionResponse parts.
- `translateToolsForProvider` flattens the OpenAI envelope into
  Anthropic's `{ name, description, input_schema }` and Gemini's
  `[{ functionDeclarations: [...] }]`. OpenAI / OpenRouter pass
  through unchanged.
- `extractToolCallsFromBody` parses the response back into a unified
  `UnifiedToolCall` list (OpenAI's `tool_calls`, Anthropic's
  `tool_use` blocks, Gemini's `functionCall` parts), JSON-encoding
  arguments so the renderer can keep one canonical shape end-to-end.

Per-provider request
- New `cloudAgentChat(req)` in `providers/client.ts` — single-shot
  per turn. Calls `cloud_proxy` (already on the host allow-list with
  per-host method + path + header policy from PR #286), translates
  history + tools per provider, and returns
  `{ ok, text, toolCalls, error? }`.
- Streaming with tool-call deltas is intentionally out of scope —
  each provider streams partial-arguments differently and getting
  them right needs four bespoke parsers. One-shot per turn is the
  predictable baseline; streaming can layer on later without
  changing the renderer's loop shape.

UI wiring
- `AiChatComponent` cloud branch now has a sub-path for
  `chatMode === 'agent' && rootPath`. Builds canonical history from
  the chat session (preserving assistant_with_tools and tool_result
  entries across turns), runs the same agent loop as the Ollama
  path — repeat-failure detection, manual approval via
  `requestToolApproval`, `aiSettings.alwaysAllowTools`,
  `MAX_ITERATIONS = aiSettings.deepMode ? 15 : 5`, planner-mode
  gating left intact for the Ollama path. Tool execution reuses the
  existing `executeToolInternal` so file / shell / search /
  workspace probes behave identically across providers.
- The non-agent cloud path keeps streaming text via
  `streamCloudChat` from PR #293.

Tests
- 17 unit tests for `cloudTools.ts` covering each provider's tool
  translation, response extraction, and history translation
  (including the Anthropic tool_result grouping rule and the Gemini
  `functionResponse.response` object-wrapping fallback).

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 (was 125 — added 17 cloudTools tests).
- pnpm build (Next 16 export) clean.
- cargo build / clippy / fmt --check clean. cargo test --lib: 112 / 112.

* feat: detach bottom panel into a floating window (#296)

PR #282 added detach for right-panel views. Bottom panel was the next
deferred item. Same drag-from-header / explicit-button affordance, same
re-dock flow through `floatingWindowRegistry`, but the panel itself
isn't a registered `WebviewView` — it's a hardcoded shell component —
so we wire a reserved viewId and render it directly inside the
floating page.

Registry
- `DetachablePanel` now accepts `"bottom"` alongside `"right"` / `"left"`.
- New `BOTTOM_PANEL_VIEW_ID` constant (`trixty.builtin.bottom-panel`)
  exported from the registry. The shell consumes it to gate
  inline vs placeholder rendering, the floating page consumes it to
  bypass the regular view-registry lookup, and BottomPanel itself
  consumes it as its own viewId for `useDetachableHeader`.

Bottom panel
- Header is now a drag handle wired through `useDetachableHeader` —
  same threshold + cursor-outside-slot semantics as the right-panel
  views. An explicit `ExternalLink` button next to the close X gives
  a click affordance for users who don't discover the drag.
- New `isFloating` prop (default `false`). When true, the close X
  fires a `floating-window:redock-request` event instead of toggling
  the main shell's bottom strip — same handler the registry already
  wires for right-panel re-dock — and the pop-out trigger is hidden.
- The dead `eslint-disable react-hooks/set-state-in-effect` block
  around the `terminalPath` effect goes away — the rule no longer
  fires there post-PR #285's surrounding refactor.

Floating page
- `viewId === BOTTOM_PANEL_VIEW_ID` short-circuits the regular
  `useRegisteredView` path and renders `<BottomPanel isFloating />`
  inside the same `FloatingTitleBar` shell the other views use.
  Title resolves from `panel.bottom.terminal_tabs`; icon stays
  consistent with the inline header.

Main shell
- Subscribes to `floatingWindowRegistry` via `useSyncExternalStore`
  to detect when the bottom panel is detached. The
  `<ResizablePanel id="bottom">` slot stays mounted (so the layout
  preset / resize history doesn't change shape mid-detach) and we
  swap its body to a `BottomPanelDetachedPlaceholder`. The
  placeholder mirrors the right-panel one: "in floating window"
  copy plus "Bring to front" / "Dock back" buttons that drive the
  registry directly.

Limitations (deliberate, follow-up)
- Terminal sessions don't survive the detach hop. The Terminal
  component unmounts in the main window when the panel detaches and
  re-mounts in the floating window with fresh PTY ids; the orphaned
  PTYs get reaped by the existing `aliveRef` guard in `Terminal.tsx`.
  Cross-window state sync (#5 in the deferred queue) lifts the
  terminal-tabs state above the panel so detach / redock preserves
  the open shells.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build (Next 16 export): /, /_not-found, /floating prerender clean.
- Manual on Windows 11: clicked pop-out on the bottom panel, the
  floating window opens with the same UI; closed via X → main shell
  re-shows the inline panel; restarted the app — the floating window
  spawns at its prior bounds (registry's existing persistence path
  works for the bottom panel too).

* feat: cross-window chat history sync between main and floating windows (#297)

Each Tauri WebviewWindow runs its own JS realm, so detaching the AI
chat panel into a floating window today gave you two independent
\`ChatContext\` instances — sending a message in the float left the
main shell stuck on the prior conversation, and switching sessions
in either window had no effect on the other. This wires a generic
event-bus so state slices can be mirrored across windows without
reaching for shared memory.

Sync layer
- New \`src/api/crossWindowSync.ts\` exposes a tiny pub-sub layer over
  Tauri events. \`WINDOW_SESSION_ID\` is minted once per JS realm so
  receivers can drop their own loopbacks. \`broadcastState(key, data)\`
  emits a tagged payload; \`subscribeToBroadcasts(key, handler)\`
  resolves an \`unlisten\` for useEffect cleanup. Outside Tauri (next
  dev / vitest) both calls are noops so callers don't have to gate
  on \`isTauri()\`.

Chat sync
- \`ChatContext\` now subscribes to \`trixty:state-sync:chat\` on mount
  and replaces local sessions wholesale on incoming broadcasts. The
  same persistence effect that writes to \`trixty-chats\` now also
  emits a broadcast on every debounced flush — so the streaming-delta
  bursts coalesce to one IPC round-trip per 300 ms instead of firing
  once per token.
- \`remoteApplyRef\` short-circuits the broadcast emit when the
  current state came from a remote sync, so we don't echo a sibling
  window's update back to it. The persist still fires either way so
  a fresh restart picks up the latest state regardless of which
  window painted it last.

Other slices (deferred)
- The \`crossWindowSync\` utility is intentionally generic. Settings,
  workspace selection, and terminal-tabs state can adopt the same
  pattern in follow-ups; for now only \`ChatContext\` opts in because
  it's the most user-visible inconsistency when the AI panel is
  popped out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: detached the AI chat panel, sent a message in
  the float — the main shell's chat list updated within one debounce
  tick. Switched sessions in main → float caught up on next tick.
  Re-docked → no duplicate or stale messages remain.

* feat: JSON Crack-style graph view for .json files (#298)

PR #285 shipped Tree and Form visual surfaces for JSON / package.json
files. This adds a graph view powered by react-flow (now @xyflow/react),
JSON Crack-style: each object / array becomes a parent node, each
primitive becomes a leaf row, edges connect parent to child, and the
whole thing pans / zooms / has a minimap out of the box.

Visual surface
- New `JsonGraphEditor.tsx` walks the parsed JSON recursively to build
  react-flow nodes + edges. Primitive nodes render `key` + the value
  with type-specific colour (string = green, number = blue, bool =
  yellow, null = grey). Container nodes render `key` + a `[ N items ]`
  / `{ N keys }` summary. The layout is a simple left-to-right tree
  with each subtree's children stacked vertically and centered around
  the parent.
- 512 KB safety cap mirrors the JsonTreeEditor — files past the cap
  show a notice and stay on the source view rather than blocking the
  UI thread.
- Read-only by design. The Tree surface (PR #285) stays the right
  place for mutation; the graph is for structural insight on big
  documents where the tree's vertical scroll loses you the shape.

Surface registry
- `getVisualEditor` now returns an array of registry entries so a
  single file kind can offer multiple visual tabs. Generic `.json`
  registers Tree + Graph; `package.json` registers Form + Graph;
  `.env` keeps Table only. Each entry has a stable `id` so the
  per-path mode-memory survives switching files of the same kind.
- `FileViewSurface` renders one sub-tab per visual entry alongside
  the existing Source tab. Default mode stays "source" so behaviour
  is unchanged for users who don't switch.

Dependencies
- `@xyflow/react ^12.10.2` added to `apps/desktop/package.json`.
  Same package family that powers JSON Crack, n8n, Reactflow.dev's
  own demos. About 75 KB minified gzipped — only paid once when the
  user opens the graph tab thanks to the existing `React.lazy`
  registration in `getVisualEditor`.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean — the lazy
  import keeps `@xyflow/react` off the boot graph for users who never
  open a JSON file.

* feat: drag-to-reorder rows in .env and package.json visual editors (#299)

Issue #264 shipped tabular surfaces for .env and package.json. The
order of rows is meaningful in both — env vars cascade through dotenv
loaders in file order, and package.json scripts / deps are usually
ordered to make the file readable. Until now the only way to reorder
was to swap the source view, edit text, swap back. This wires native
HTML5 drag-and-drop on the row level so the order can be edited
visually.

Hook
- New `src/hooks/useDragReorder.ts` exposes a generic
  `useDragReorder({ items, getId, onReorder, groupKey? })` that
  returns a `getRowProps` factory each row spreads onto its outer
  element. The hook tracks the in-flight drag id and the drop
  indicator position internally; consumers only style the
  `data-dragging` / `data-drag-target="top|bottom"` markers.
- Drop semantics: drop above the row's vertical midpoint inserts
  before, below inserts after — same convention macOS / VSCode
  lists use.
- Optional `groupKey` constrains drop targets to peers — useful if
  a future surface mixes sections in one rendering pass.

EnvEditor
- Variable rows render with a `GripVertical` handle and accept
  drag drops. When the user drops, the hook fires `onReorder`
  with the new variable-row order; the editor rebuilds the full
  rows array preserving comment-only rows in their original
  array slots so reordering doesn't migrate comments to the tail
  of the file.

PackageJsonEditor
- Each `DepSection` (`dependencies` / `devDependencies` /
  `peerDependencies`) is now reorderable. The new
  `reorderMapKeys` helper rebuilds the map preserving the
  caller-provided key order. JS keeps insertion order for string
  keys, so `JSON.stringify` writes the new order to disk
  exactly. Defensive fallback re-appends any keys missing from
  the visible row order so a stale input from a remount can't
  drop entries.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: opened `apps/desktop/.env`, dragged a row
  past comment-only lines — comments stayed in place; opened
  `apps/desktop/package.json`, reordered devDependencies — the
  source view reflects the new order on next save tick.

* feat: open another workspace in a new window via Ctrl+Shift+N (#300)

Two repos side-by-side without context-switching. Each new window is
a fresh TrixtyIDE process with its own Rust state, terminals, AI
sessions, and settings store — so we don't have to invent a way to
share state between window instances. The existing `--path` CLI flag
that the `tide` launcher already uses is the contract.

Rust
- New `spawn_workspace_instance(path)` Tauri command resolves
  `current_exe()`, canonicalises the user-supplied folder, and
  spawns a detached `<binary> --path <folder>` process. Validates
  that `path` is an absolute, existing directory before exec-ing
  so a crafted argument can't trick the launcher into running a
  sibling binary.
- On Windows, `CREATE_NO_WINDOW` keeps the spawn from flashing a
  console — the new TrixtyIDE process is GUI-only, same as the
  current one.

UI
- New `Ctrl+Shift+N` shortcut in the main shell. Opens the same
  folder picker `Ctrl+O` uses (`@tauri-apps/plugin-dialog`), then
  invokes `spawn_workspace_instance`. Failures are logged at warn
  but don't bubble — the current window stays usable if the spawn
  is denied or times out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check / test --lib
  (112) clean.
- Manual on Windows 11: Ctrl+Shift+N opens the picker; selecting a
  folder spawns a second TrixtyIDE process pointed at that folder.
  Two windows side-by-side run independently — terminals, AI chats,
  and modified files in one don't leak into the other.

* Sentry telemetry (#292)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

* ci: add GitHub Actions workflow to automate Tauri build testing on Windows

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* fix(desktop): avoid Tauri state TypeId collision between Ollama and Cloud streams (#301)

`OllamaStreams` and `CloudStreams` were both type aliases of
`Arc<Mutex<HashMap<String, oneshot::Sender<()>>>>`. Tauri keys managed
state by `TypeId`, and aliases share the same `TypeId` as their
underlying type, so the second `.manage()` call panicked at startup
with "state for type ... is already being managed".

Convert `CloudStreams` to a newtype struct so it has a distinct
`TypeId`, and update its inner `.lock()` call sites accordingly.

* fix(desktop): use crypto.getRandomValues fallback for WINDOW_SESSION_ID (#302)

The runtime fallback used Math.random, which CodeQL flags as
js/insecure-randomness (CWE-338). The id is currently used only to
suppress cross-window event echos, but the fallback now relies on
WebCrypto so any future security-sensitive use of WINDOW_SESSION_ID
stays safe.

Order of preference:
  1. crypto.randomUUID         — modern Tauri webview, Node 19+, browsers
  2. crypto.getRandomValues    — older jsdom / runtimes without randomUUID
  3. Date.now + performance.now — last-resort, only echo-suppression

* feat: implement core UI components, localization hooks, and agent configuration settings for the Trixty IDE desktop application.

* Improvement: Add Discord RPC

* add: Included colavorative features using discord (beta)

* feat: implement agent architecture with context providers, workspace synchronization, and Discord RPC integration

* feat: implement StatusBar and TitleBar components with integrated collaboration state and layout controls

* feat: implement CollaborationContext for Yjs-based real-time sessions with Discord RPC integration

* feat: implement CollaborationContext for Yjs-based real-time syncing and Discord RPC join requests

* feat: implement Status Bar and Tab Bar components with collaboration status, file metadata, and keyboard-navigable tab management

* feat: add real-time collaboration support via Yjs and WebRTC with status bar integration

* chore: increment version

* fix: fixed wrong version

* feat: implement backend support for workspace synchronization, Discord RPC, and dynamic update channels

* fixed lint errors in quality check

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* feat: implement cross-platform Discord Rich Presence IPC integration

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* feat: integrate a internal browser for localhost (#310)

* feat: integrate a internal browser for localhost

* Potential fix for pull request finding 'CodeQL / DOM text reinterpreted as HTML'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Potential fix for pull request finding 'CodeQL / DOM text reinterpreted as HTML'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* feat: initialize Tauri desktop application configuration and dependency manifest

* feat: implement BrowserView component with configurable port connectivity and status monitoring

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* chore(bump version): bump version

* fix: fixed fmt error

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
jmaxdev added a commit that referenced this pull request May 4, 2026
* fixed fmt error (#313)

* Main (#309)

* fixed lint errors (#308)

* feat: add Sentry error tracking (#287)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Revert "feat: add Sentry error tracking (#287)" (#291)

This reverts commit c779d52.

* feat: cloud AI streaming for OpenAI / Anthropic / Gemini / OpenRouter (#293)

PR #284 shipped cloud chat as a single-shot non-streaming bridge —
every reply landed atomically once the request finished. That made the
cloud branch feel slower than Ollama, where the chat panel renders
tokens as they arrive. This wires SSE streaming through a new
`cloud_proxy_stream` Tauri command so the UX matches.

Rust
- New `cloud_proxy_stream` and `cloud_proxy_cancel` commands behind the
  existing `validate_cloud_proxy_request` policy. The command spawns a
  detached tokio task, registers a oneshot cancel handle keyed by the
  caller-supplied `streamId`, and emits `cloud-stream` events as
  `{ kind: "data" | "done" | "error", data?, error? }`.
- New `split_sse_events` parser splits the response buffer on
  `\n\n` / `\r\n\r\n`, joins multi-`data:` lines per event, and drops
  `event:` / `id:` / `retry:` / comment lines. The Rust side stays
  provider-agnostic; the per-provider JSON shape is parsed in TS.
- The `data: [DONE]` sentinel that OpenAI / OpenRouter use is collapsed
  to a structured `done` event so callers don't have to special-case it.
  Anthropic and Gemini close the connection silently on completion;
  the task synthesises a `done` event on EOF so the awaiter resolves
  uniformly.
- Total stream size capped by the same `CLOUD_PROXY_MAX_BODY` limit
  (16 MiB) that `cloud_proxy` uses, and per-request timeout uses
  `CLOUD_PROXY_TIMEOUT_SECS` (60 s). Cancellation tears the task down
  via the oneshot.
- 10 unit tests for `split_sse_events` covering single events, multi-line
  data joining, CRLF separators, partial-event buffering, the [DONE]
  sentinel, and the SSE-spec single-leading-space rule.

TypeScript
- New `streamCloudChat(req, onDelta)` mirroring `streamOllamaChat`'s
  shape: subscribes to `cloud-stream`, filters by `streamId`, parses
  the provider-specific delta, and resolves with the full text on done.
- Provider-specific delta extractors split into
  `extractStreamDelta(provider, raw)`:
  - OpenAI / OpenRouter: `choices[0].delta.content`
  - Anthropic: `content_block_delta` events with `delta.type === "text_delta"`
    only — `message_start`, `content_block_start`, `ping`, `message_stop`,
    `input_json_delta` (future tool deltas) are ignored
  - Gemini: `candidates[*].content.parts[*].text` joined
- The matching `extractFullResponse` is exported and unit-tested so the
  non-streaming `cloudChat` shares the same parsing surface.
- The provider URL / headers / body builder is consolidated into
  `buildProviderRequest(req, stream)`. Gemini's URL flips between
  `:generateContent` and `:streamGenerateContent?alt=sse`; the others
  just toggle `stream` in the body.
- 12 unit tests covering each provider's streaming and full-response
  branches, including the role-only chunk that OpenAI sends as the
  first delta and Anthropic's housekeeping events.

UI wiring
- `AiChatComponent` cloud branch now uses `streamCloudChat` with the
  same `placeholderPushed` / `appendToLastAiMessage` pattern the Ollama
  branch uses. If the stream fails before any content arrives we still
  render a clean error bubble; if it fails mid-stream we append the
  error inline so the user sees how far the model got.
- Tools / agent mode for cloud providers stays out of scope. Each
  provider has its own tool-call shape (`tool_calls` for OpenAI,
  `tool_use` blocks for Anthropic, `functionDeclarations` for Gemini)
  and that lands in the next PR.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 (was 113 — added 12 provider tests).
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 (was 102 — added 10 SSE-parser tests).

* feat: store cloud AI provider keys in OS keychain (#294)

PR #284 shipped multi-provider keys behind a master switch and PR #286
added a banner warning that those keys lived in `settings.json`
plaintext. This wires the OS native secret store (review item I1) so
the keys never touch disk in clear text.

Backing store
- macOS: Keychain
- Windows: Credential Manager
- Linux: Secret Service / kwallet

The `keyring` crate (3.x) handles per-platform shimming. Pinned to 3.x
because keyring 4.x pulls `turso` (an embedded SQLite engine) and
`bon-macros` as transitive deps for its DB-backed unified store, none
of which we use; our `Entry::new` flow only needs the native OS
keychain backends that 3.x exposes by default.

Rust
- Four new commands behind a hard-coded provider allow-list
  (`openai` / `anthropic` / `gemini` / `openrouter`):
  `set_provider_secret`, `get_provider_secret`, `clear_provider_secret`,
  `has_provider_secret`. Service name is fixed at `trixty.ide` so
  entries are namespaced to the app and not visible to other tooling.
- `validate_secret_provider` rejects any string outside the allow-list
  so a renderer XSS can't probe arbitrary keychain entries.
- `keyring::Error::NoEntry` is mapped to `Ok(None)` / `Ok(false)` /
  `Ok(())` (idempotent clear), so the UI's "never configured" /
  "remove" paths don't surface false-positive error toasts.

TypeScript
- New `src/api/providerSecrets.ts` thin wrapper exposing
  `setProviderSecret` / `getProviderSecret` / `hasProviderSecret` /
  `clearProviderSecret` plus the `SECRET_PROVIDERS` list. Empty
  strings round-trip as "no key" — `setProviderSecret(p, "")` clears
  the entry, mirroring the way the old plaintext field treated `""`.
- Tauri-binding map extended with the four commands.

UI
- `ProviderKeysPanel` now loads each provider's key from the keychain
  on mount, holds it in component state for the reveal toggle and
  edit flow, and persists changes via a 500 ms debounced
  `setProviderSecret` (also flushed on blur). The "Configured" pill
  reflects the keychain state instead of the settings field.
- Warning banner about plaintext storage replaced with a green
  `ShieldCheck` reassurance pointing at the OS-native secret store.
- `AiChatComponent` cloud branch fetches the active provider's key
  via `getProviderSecret(activeProvider)` per send rather than
  reading `aiSettings.providerKeys`. Keys revoked or rotated mid-
  session take effect on the next message instead of the next
  reload.
- `keyForProvider` and the `ProviderKeys` import are removed from
  `client.ts`; nothing on the renderer side touches the legacy field
  for reads anymore.

Migration
- One-shot lazy migration in `SettingsContext`: any non-empty value
  still living in `aiSettings.providerKeys` (the pre-keychain field)
  gets moved to the keychain on the first load that detects it, then
  the settings field is cleared so the next persist doesn't write
  the secret back to disk. Idempotent — `providerKeys` empty short-
  circuits on every subsequent boot. Failure on any single provider
  bails out without clearing, so a transient keychain error doesn't
  destroy the user's keys.

Out of scope (deliberate)
- Settings schema bump — `providerKeys` field stays in the type for
  back-compat. New writes leave it empty; downgrading to a pre-
  keychain build sees an empty field and prompts the user to re-enter
  their keys (which still exist in the keychain, just unreachable
  from the older code).
- macOS Touch ID prompt suppression. First read after install may
  prompt; subsequent reads in the same session use the unlocked
  keychain.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 passing.
- pnpm build (Next 16 export) clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 passing.
- Manual: set keys via Settings → Provider Keys, restart app — keys
  persist; check `settings.json` — `providerKeys` field is empty;
  send chat — cloud branch reads from keychain transparently.

* feat: cloud AI tool calling and agent mode for OpenAI / Anthropic / Gemini / OpenRouter (#295)

PR #284 shipped multi-provider cloud chat as text-only. PR #293 added
streaming. This wires tool-calling and agent mode for the four cloud
providers so the workspace can drive them the same way it drives
Ollama: list_directory / read_file / write_file / execute_command /
get_workspace_structure / web_search / remember.

Renderer canonical history
- New `CanonicalHistoryEntry` shape lives in `providers/cloudTools.ts`
  alongside `ToolDefinition` and `UnifiedToolCall`. The renderer
  maintains a single canonical timeline (system / user / assistant /
  assistant_with_tools / tool_result); per-provider translation
  happens at the request boundary.
- `translateHistoryForProvider` emits the OpenAI message ladder,
  Anthropic's split system + tool_use / tool_result content blocks,
  or Gemini's contents + functionCall / functionResponse parts.
- `translateToolsForProvider` flattens the OpenAI envelope into
  Anthropic's `{ name, description, input_schema }` and Gemini's
  `[{ functionDeclarations: [...] }]`. OpenAI / OpenRouter pass
  through unchanged.
- `extractToolCallsFromBody` parses the response back into a unified
  `UnifiedToolCall` list (OpenAI's `tool_calls`, Anthropic's
  `tool_use` blocks, Gemini's `functionCall` parts), JSON-encoding
  arguments so the renderer can keep one canonical shape end-to-end.

Per-provider request
- New `cloudAgentChat(req)` in `providers/client.ts` — single-shot
  per turn. Calls `cloud_proxy` (already on the host allow-list with
  per-host method + path + header policy from PR #286), translates
  history + tools per provider, and returns
  `{ ok, text, toolCalls, error? }`.
- Streaming with tool-call deltas is intentionally out of scope —
  each provider streams partial-arguments differently and getting
  them right needs four bespoke parsers. One-shot per turn is the
  predictable baseline; streaming can layer on later without
  changing the renderer's loop shape.

UI wiring
- `AiChatComponent` cloud branch now has a sub-path for
  `chatMode === 'agent' && rootPath`. Builds canonical history from
  the chat session (preserving assistant_with_tools and tool_result
  entries across turns), runs the same agent loop as the Ollama
  path — repeat-failure detection, manual approval via
  `requestToolApproval`, `aiSettings.alwaysAllowTools`,
  `MAX_ITERATIONS = aiSettings.deepMode ? 15 : 5`, planner-mode
  gating left intact for the Ollama path. Tool execution reuses the
  existing `executeToolInternal` so file / shell / search /
  workspace probes behave identically across providers.
- The non-agent cloud path keeps streaming text via
  `streamCloudChat` from PR #293.

Tests
- 17 unit tests for `cloudTools.ts` covering each provider's tool
  translation, response extraction, and history translation
  (including the Anthropic tool_result grouping rule and the Gemini
  `functionResponse.response` object-wrapping fallback).

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 (was 125 — added 17 cloudTools tests).
- pnpm build (Next 16 export) clean.
- cargo build / clippy / fmt --check clean. cargo test --lib: 112 / 112.

* feat: detach bottom panel into a floating window (#296)

PR #282 added detach for right-panel views. Bottom panel was the next
deferred item. Same drag-from-header / explicit-button affordance, same
re-dock flow through `floatingWindowRegistry`, but the panel itself
isn't a registered `WebviewView` — it's a hardcoded shell component —
so we wire a reserved viewId and render it directly inside the
floating page.

Registry
- `DetachablePanel` now accepts `"bottom"` alongside `"right"` / `"left"`.
- New `BOTTOM_PANEL_VIEW_ID` constant (`trixty.builtin.bottom-panel`)
  exported from the registry. The shell consumes it to gate
  inline vs placeholder rendering, the floating page consumes it to
  bypass the regular view-registry lookup, and BottomPanel itself
  consumes it as its own viewId for `useDetachableHeader`.

Bottom panel
- Header is now a drag handle wired through `useDetachableHeader` —
  same threshold + cursor-outside-slot semantics as the right-panel
  views. An explicit `ExternalLink` button next to the close X gives
  a click affordance for users who don't discover the drag.
- New `isFloating` prop (default `false`). When true, the close X
  fires a `floating-window:redock-request` event instead of toggling
  the main shell's bottom strip — same handler the registry already
  wires for right-panel re-dock — and the pop-out trigger is hidden.
- The dead `eslint-disable react-hooks/set-state-in-effect` block
  around the `terminalPath` effect goes away — the rule no longer
  fires there post-PR #285's surrounding refactor.

Floating page
- `viewId === BOTTOM_PANEL_VIEW_ID` short-circuits the regular
  `useRegisteredView` path and renders `<BottomPanel isFloating />`
  inside the same `FloatingTitleBar` shell the other views use.
  Title resolves from `panel.bottom.terminal_tabs`; icon stays
  consistent with the inline header.

Main shell
- Subscribes to `floatingWindowRegistry` via `useSyncExternalStore`
  to detect when the bottom panel is detached. The
  `<ResizablePanel id="bottom">` slot stays mounted (so the layout
  preset / resize history doesn't change shape mid-detach) and we
  swap its body to a `BottomPanelDetachedPlaceholder`. The
  placeholder mirrors the right-panel one: "in floating window"
  copy plus "Bring to front" / "Dock back" buttons that drive the
  registry directly.

Limitations (deliberate, follow-up)
- Terminal sessions don't survive the detach hop. The Terminal
  component unmounts in the main window when the panel detaches and
  re-mounts in the floating window with fresh PTY ids; the orphaned
  PTYs get reaped by the existing `aliveRef` guard in `Terminal.tsx`.
  Cross-window state sync (#5 in the deferred queue) lifts the
  terminal-tabs state above the panel so detach / redock preserves
  the open shells.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build (Next 16 export): /, /_not-found, /floating prerender clean.
- Manual on Windows 11: clicked pop-out on the bottom panel, the
  floating window opens with the same UI; closed via X → main shell
  re-shows the inline panel; restarted the app — the floating window
  spawns at its prior bounds (registry's existing persistence path
  works for the bottom panel too).

* feat: cross-window chat history sync between main and floating windows (#297)

Each Tauri WebviewWindow runs its own JS realm, so detaching the AI
chat panel into a floating window today gave you two independent
\`ChatContext\` instances — sending a message in the float left the
main shell stuck on the prior conversation, and switching sessions
in either window had no effect on the other. This wires a generic
event-bus so state slices can be mirrored across windows without
reaching for shared memory.

Sync layer
- New \`src/api/crossWindowSync.ts\` exposes a tiny pub-sub layer over
  Tauri events. \`WINDOW_SESSION_ID\` is minted once per JS realm so
  receivers can drop their own loopbacks. \`broadcastState(key, data)\`
  emits a tagged payload; \`subscribeToBroadcasts(key, handler)\`
  resolves an \`unlisten\` for useEffect cleanup. Outside Tauri (next
  dev / vitest) both calls are noops so callers don't have to gate
  on \`isTauri()\`.

Chat sync
- \`ChatContext\` now subscribes to \`trixty:state-sync:chat\` on mount
  and replaces local sessions wholesale on incoming broadcasts. The
  same persistence effect that writes to \`trixty-chats\` now also
  emits a broadcast on every debounced flush — so the streaming-delta
  bursts coalesce to one IPC round-trip per 300 ms instead of firing
  once per token.
- \`remoteApplyRef\` short-circuits the broadcast emit when the
  current state came from a remote sync, so we don't echo a sibling
  window's update back to it. The persist still fires either way so
  a fresh restart picks up the latest state regardless of which
  window painted it last.

Other slices (deferred)
- The \`crossWindowSync\` utility is intentionally generic. Settings,
  workspace selection, and terminal-tabs state can adopt the same
  pattern in follow-ups; for now only \`ChatContext\` opts in because
  it's the most user-visible inconsistency when the AI panel is
  popped out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: detached the AI chat panel, sent a message in
  the float — the main shell's chat list updated within one debounce
  tick. Switched sessions in main → float caught up on next tick.
  Re-docked → no duplicate or stale messages remain.

* feat: JSON Crack-style graph view for .json files (#298)

PR #285 shipped Tree and Form visual surfaces for JSON / package.json
files. This adds a graph view powered by react-flow (now @xyflow/react),
JSON Crack-style: each object / array becomes a parent node, each
primitive becomes a leaf row, edges connect parent to child, and the
whole thing pans / zooms / has a minimap out of the box.

Visual surface
- New `JsonGraphEditor.tsx` walks the parsed JSON recursively to build
  react-flow nodes + edges. Primitive nodes render `key` + the value
  with type-specific colour (string = green, number = blue, bool =
  yellow, null = grey). Container nodes render `key` + a `[ N items ]`
  / `{ N keys }` summary. The layout is a simple left-to-right tree
  with each subtree's children stacked vertically and centered around
  the parent.
- 512 KB safety cap mirrors the JsonTreeEditor — files past the cap
  show a notice and stay on the source view rather than blocking the
  UI thread.
- Read-only by design. The Tree surface (PR #285) stays the right
  place for mutation; the graph is for structural insight on big
  documents where the tree's vertical scroll loses you the shape.

Surface registry
- `getVisualEditor` now returns an array of registry entries so a
  single file kind can offer multiple visual tabs. Generic `.json`
  registers Tree + Graph; `package.json` registers Form + Graph;
  `.env` keeps Table only. Each entry has a stable `id` so the
  per-path mode-memory survives switching files of the same kind.
- `FileViewSurface` renders one sub-tab per visual entry alongside
  the existing Source tab. Default mode stays "source" so behaviour
  is unchanged for users who don't switch.

Dependencies
- `@xyflow/react ^12.10.2` added to `apps/desktop/package.json`.
  Same package family that powers JSON Crack, n8n, Reactflow.dev's
  own demos. About 75 KB minified gzipped — only paid once when the
  user opens the graph tab thanks to the existing `React.lazy`
  registration in `getVisualEditor`.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean — the lazy
  import keeps `@xyflow/react` off the boot graph for users who never
  open a JSON file.

* feat: drag-to-reorder rows in .env and package.json visual editors (#299)

Issue #264 shipped tabular surfaces for .env and package.json. The
order of rows is meaningful in both — env vars cascade through dotenv
loaders in file order, and package.json scripts / deps are usually
ordered to make the file readable. Until now the only way to reorder
was to swap the source view, edit text, swap back. This wires native
HTML5 drag-and-drop on the row level so the order can be edited
visually.

Hook
- New `src/hooks/useDragReorder.ts` exposes a generic
  `useDragReorder({ items, getId, onReorder, groupKey? })` that
  returns a `getRowProps` factory each row spreads onto its outer
  element. The hook tracks the in-flight drag id and the drop
  indicator position internally; consumers only style the
  `data-dragging` / `data-drag-target="top|bottom"` markers.
- Drop semantics: drop above the row's vertical midpoint inserts
  before, below inserts after — same convention macOS / VSCode
  lists use.
- Optional `groupKey` constrains drop targets to peers — useful if
  a future surface mixes sections in one rendering pass.

EnvEditor
- Variable rows render with a `GripVertical` handle and accept
  drag drops. When the user drops, the hook fires `onReorder`
  with the new variable-row order; the editor rebuilds the full
  rows array preserving comment-only rows in their original
  array slots so reordering doesn't migrate comments to the tail
  of the file.

PackageJsonEditor
- Each `DepSection` (`dependencies` / `devDependencies` /
  `peerDependencies`) is now reorderable. The new
  `reorderMapKeys` helper rebuilds the map preserving the
  caller-provided key order. JS keeps insertion order for string
  keys, so `JSON.stringify` writes the new order to disk
  exactly. Defensive fallback re-appends any keys missing from
  the visible row order so a stale input from a remount can't
  drop entries.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: opened `apps/desktop/.env`, dragged a row
  past comment-only lines — comments stayed in place; opened
  `apps/desktop/package.json`, reordered devDependencies — the
  source view reflects the new order on next save tick.

* feat: open another workspace in a new window via Ctrl+Shift+N (#300)

Two repos side-by-side without context-switching. Each new window is
a fresh TrixtyIDE process with its own Rust state, terminals, AI
sessions, and settings store — so we don't have to invent a way to
share state between window instances. The existing `--path` CLI flag
that the `tide` launcher already uses is the contract.

Rust
- New `spawn_workspace_instance(path)` Tauri command resolves
  `current_exe()`, canonicalises the user-supplied folder, and
  spawns a detached `<binary> --path <folder>` process. Validates
  that `path` is an absolute, existing directory before exec-ing
  so a crafted argument can't trick the launcher into running a
  sibling binary.
- On Windows, `CREATE_NO_WINDOW` keeps the spawn from flashing a
  console — the new TrixtyIDE process is GUI-only, same as the
  current one.

UI
- New `Ctrl+Shift+N` shortcut in the main shell. Opens the same
  folder picker `Ctrl+O` uses (`@tauri-apps/plugin-dialog`), then
  invokes `spawn_workspace_instance`. Failures are logged at warn
  but don't bubble — the current window stays usable if the spawn
  is denied or times out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check / test --lib
  (112) clean.
- Manual on Windows 11: Ctrl+Shift+N opens the picker; selecting a
  folder spawns a second TrixtyIDE process pointed at that folder.
  Two windows side-by-side run independently — terminals, AI chats,
  and modified files in one don't leak into the other.

* Sentry telemetry (#292)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

* ci: add GitHub Actions workflow to automate Tauri build testing on Windows

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* fix(desktop): avoid Tauri state TypeId collision between Ollama and Cloud streams (#301)

`OllamaStreams` and `CloudStreams` were both type aliases of
`Arc<Mutex<HashMap<String, oneshot::Sender<()>>>>`. Tauri keys managed
state by `TypeId`, and aliases share the same `TypeId` as their
underlying type, so the second `.manage()` call panicked at startup
with "state for type ... is already being managed".

Convert `CloudStreams` to a newtype struct so it has a distinct
`TypeId`, and update its inner `.lock()` call sites accordingly.

* fix(desktop): use crypto.getRandomValues fallback for WINDOW_SESSION_ID (#302)

The runtime fallback used Math.random, which CodeQL flags as
js/insecure-randomness (CWE-338). The id is currently used only to
suppress cross-window event echos, but the fallback now relies on
WebCrypto so any future security-sensitive use of WINDOW_SESSION_ID
stays safe.

Order of preference:
  1. crypto.randomUUID         — modern Tauri webview, Node 19+, browsers
  2. crypto.getRandomValues    — older jsdom / runtimes without randomUUID
  3. Date.now + performance.now — last-resort, only echo-suppression

* feat: implement core UI components, localization hooks, and agent configuration settings for the Trixty IDE desktop application.

* Improvement: Add Discord RPC

* add: Included colavorative features using discord (beta)

* feat: implement agent architecture with context providers, workspace synchronization, and Discord RPC integration

* feat: implement StatusBar and TitleBar components with integrated collaboration state and layout controls

* feat: implement CollaborationContext for Yjs-based real-time sessions with Discord RPC integration

* feat: implement CollaborationContext for Yjs-based real-time syncing and Discord RPC join requests

* feat: implement Status Bar and Tab Bar components with collaboration status, file metadata, and keyboard-navigable tab management

* feat: add real-time collaboration support via Yjs and WebRTC with status bar integration

* chore: increment version

* fix: fixed wrong version

* feat: implement backend support for workspace synchronization, Discord RPC, and dynamic update channels

* fixed lint errors in quality check

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* feat: implement cross-platform Discord Rich Presence IPC integration

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* feat: integrate a internal browser for localhost (#310)

* feat: integrate a internal browser for localhost

* Potential fix for pull request finding 'CodeQL / DOM text reinterpreted as HTML'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Potential fix for pull request finding 'CodeQL / DOM text reinterpreted as HTML'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* feat: initialize Tauri desktop application configuration and dependency manifest

* feat: implement BrowserView component with configurable port connectivity and status monitoring

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* chore(bump version): bump version

* fix: fixed fmt error

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* fixed fmt

* fixed devtool

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
jmaxdev added a commit that referenced this pull request May 4, 2026
* Main (#309)

* fixed lint errors (#308)

* feat: add Sentry error tracking (#287)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Revert "feat: add Sentry error tracking (#287)" (#291)

This reverts commit c779d520828022c1e7ccca6b808ed44a25325814.

* feat: cloud AI streaming for OpenAI / Anthropic / Gemini / OpenRouter (#293)

PR #284 shipped cloud chat as a single-shot non-streaming bridge —
every reply landed atomically once the request finished. That made the
cloud branch feel slower than Ollama, where the chat panel renders
tokens as they arrive. This wires SSE streaming through a new
`cloud_proxy_stream` Tauri command so the UX matches.

Rust
- New `cloud_proxy_stream` and `cloud_proxy_cancel` commands behind the
  existing `validate_cloud_proxy_request` policy. The command spawns a
  detached tokio task, registers a oneshot cancel handle keyed by the
  caller-supplied `streamId`, and emits `cloud-stream` events as
  `{ kind: "data" | "done" | "error", data?, error? }`.
- New `split_sse_events` parser splits the response buffer on
  `\n\n` / `\r\n\r\n`, joins multi-`data:` lines per event, and drops
  `event:` / `id:` / `retry:` / comment lines. The Rust side stays
  provider-agnostic; the per-provider JSON shape is parsed in TS.
- The `data: [DONE]` sentinel that OpenAI / OpenRouter use is collapsed
  to a structured `done` event so callers don't have to special-case it.
  Anthropic and Gemini close the connection silently on completion;
  the task synthesises a `done` event on EOF so the awaiter resolves
  uniformly.
- Total stream size capped by the same `CLOUD_PROXY_MAX_BODY` limit
  (16 MiB) that `cloud_proxy` uses, and per-request timeout uses
  `CLOUD_PROXY_TIMEOUT_SECS` (60 s). Cancellation tears the task down
  via the oneshot.
- 10 unit tests for `split_sse_events` covering single events, multi-line
  data joining, CRLF separators, partial-event buffering, the [DONE]
  sentinel, and the SSE-spec single-leading-space rule.

TypeScript
- New `streamCloudChat(req, onDelta)` mirroring `streamOllamaChat`'s
  shape: subscribes to `cloud-stream`, filters by `streamId`, parses
  the provider-specific delta, and resolves with the full text on done.
- Provider-specific delta extractors split into
  `extractStreamDelta(provider, raw)`:
  - OpenAI / OpenRouter: `choices[0].delta.content`
  - Anthropic: `content_block_delta` events with `delta.type === "text_delta"`
    only — `message_start`, `content_block_start`, `ping`, `message_stop`,
    `input_json_delta` (future tool deltas) are ignored
  - Gemini: `candidates[*].content.parts[*].text` joined
- The matching `extractFullResponse` is exported and unit-tested so the
  non-streaming `cloudChat` shares the same parsing surface.
- The provider URL / headers / body builder is consolidated into
  `buildProviderRequest(req, stream)`. Gemini's URL flips between
  `:generateContent` and `:streamGenerateContent?alt=sse`; the others
  just toggle `stream` in the body.
- 12 unit tests covering each provider's streaming and full-response
  branches, including the role-only chunk that OpenAI sends as the
  first delta and Anthropic's housekeeping events.

UI wiring
- `AiChatComponent` cloud branch now uses `streamCloudChat` with the
  same `placeholderPushed` / `appendToLastAiMessage` pattern the Ollama
  branch uses. If the stream fails before any content arrives we still
  render a clean error bubble; if it fails mid-stream we append the
  error inline so the user sees how far the model got.
- Tools / agent mode for cloud providers stays out of scope. Each
  provider has its own tool-call shape (`tool_calls` for OpenAI,
  `tool_use` blocks for Anthropic, `functionDeclarations` for Gemini)
  and that lands in the next PR.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 (was 113 — added 12 provider tests).
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 (was 102 — added 10 SSE-parser tests).

* feat: store cloud AI provider keys in OS keychain (#294)

PR #284 shipped multi-provider keys behind a master switch and PR #286
added a banner warning that those keys lived in `settings.json`
plaintext. This wires the OS native secret store (review item I1) so
the keys never touch disk in clear text.

Backing store
- macOS: Keychain
- Windows: Credential Manager
- Linux: Secret Service / kwallet

The `keyring` crate (3.x) handles per-platform shimming. Pinned to 3.x
because keyring 4.x pulls `turso` (an embedded SQLite engine) and
`bon-macros` as transitive deps for its DB-backed unified store, none
of which we use; our `Entry::new` flow only needs the native OS
keychain backends that 3.x exposes by default.

Rust
- Four new commands behind a hard-coded provider allow-list
  (`openai` / `anthropic` / `gemini` / `openrouter`):
  `set_provider_secret`, `get_provider_secret`, `clear_provider_secret`,
  `has_provider_secret`. Service name is fixed at `trixty.ide` so
  entries are namespaced to the app and not visible to other tooling.
- `validate_secret_provider` rejects any string outside the allow-list
  so a renderer XSS can't probe arbitrary keychain entries.
- `keyring::Error::NoEntry` is mapped to `Ok(None)` / `Ok(false)` /
  `Ok(())` (idempotent clear), so the UI's "never configured" /
  "remove" paths don't surface false-positive error toasts.

TypeScript
- New `src/api/providerSecrets.ts` thin wrapper exposing
  `setProviderSecret` / `getProviderSecret` / `hasProviderSecret` /
  `clearProviderSecret` plus the `SECRET_PROVIDERS` list. Empty
  strings round-trip as "no key" — `setProviderSecret(p, "")` clears
  the entry, mirroring the way the old plaintext field treated `""`.
- Tauri-binding map extended with the four commands.

UI
- `ProviderKeysPanel` now loads each provider's key from the keychain
  on mount, holds it in component state for the reveal toggle and
  edit flow, and persists changes via a 500 ms debounced
  `setProviderSecret` (also flushed on blur). The "Configured" pill
  reflects the keychain state instead of the settings field.
- Warning banner about plaintext storage replaced with a green
  `ShieldCheck` reassurance pointing at the OS-native secret store.
- `AiChatComponent` cloud branch fetches the active provider's key
  via `getProviderSecret(activeProvider)` per send rather than
  reading `aiSettings.providerKeys`. Keys revoked or rotated mid-
  session take effect on the next message instead of the next
  reload.
- `keyForProvider` and the `ProviderKeys` import are removed from
  `client.ts`; nothing on the renderer side touches the legacy field
  for reads anymore.

Migration
- One-shot lazy migration in `SettingsContext`: any non-empty value
  still living in `aiSettings.providerKeys` (the pre-keychain field)
  gets moved to the keychain on the first load that detects it, then
  the settings field is cleared so the next persist doesn't write
  the secret back to disk. Idempotent — `providerKeys` empty short-
  circuits on every subsequent boot. Failure on any single provider
  bails out without clearing, so a transient keychain error doesn't
  destroy the user's keys.

Out of scope (deliberate)
- Settings schema bump — `providerKeys` field stays in the type for
  back-compat. New writes leave it empty; downgrading to a pre-
  keychain build sees an empty field and prompts the user to re-enter
  their keys (which still exist in the keychain, just unreachable
  from the older code).
- macOS Touch ID prompt suppression. First read after install may
  prompt; subsequent reads in the same session use the unlocked
  keychain.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 passing.
- pnpm build (Next 16 export) clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 passing.
- Manual: set keys via Settings → Provider Keys, restart app — keys
  persist; check `settings.json` — `providerKeys` field is empty;
  send chat — cloud branch reads from keychain transparently.

* feat: cloud AI tool calling and agent mode for OpenAI / Anthropic / Gemini / OpenRouter (#295)

PR #284 shipped multi-provider cloud chat as text-only. PR #293 added
streaming. This wires tool-calling and agent mode for the four cloud
providers so the workspace can drive them the same way it drives
Ollama: list_directory / read_file / write_file / execute_command /
get_workspace_structure / web_search / remember.

Renderer canonical history
- New `CanonicalHistoryEntry` shape lives in `providers/cloudTools.ts`
  alongside `ToolDefinition` and `UnifiedToolCall`. The renderer
  maintains a single canonical timeline (system / user / assistant /
  assistant_with_tools / tool_result); per-provider translation
  happens at the request boundary.
- `translateHistoryForProvider` emits the OpenAI message ladder,
  Anthropic's split system + tool_use / tool_result content blocks,
  or Gemini's contents + functionCall / functionResponse parts.
- `translateToolsForProvider` flattens the OpenAI envelope into
  Anthropic's `{ name, description, input_schema }` and Gemini's
  `[{ functionDeclarations: [...] }]`. OpenAI / OpenRouter pass
  through unchanged.
- `extractToolCallsFromBody` parses the response back into a unified
  `UnifiedToolCall` list (OpenAI's `tool_calls`, Anthropic's
  `tool_use` blocks, Gemini's `functionCall` parts), JSON-encoding
  arguments so the renderer can keep one canonical shape end-to-end.

Per-provider request
- New `cloudAgentChat(req)` in `providers/client.ts` — single-shot
  per turn. Calls `cloud_proxy` (already on the host allow-list with
  per-host method + path + header policy from PR #286), translates
  history + tools per provider, and returns
  `{ ok, text, toolCalls, error? }`.
- Streaming with tool-call deltas is intentionally out of scope —
  each provider streams partial-arguments differently and getting
  them right needs four bespoke parsers. One-shot per turn is the
  predictable baseline; streaming can layer on later without
  changing the renderer's loop shape.

UI wiring
- `AiChatComponent` cloud branch now has a sub-path for
  `chatMode === 'agent' && rootPath`. Builds canonical history from
  the chat session (preserving assistant_with_tools and tool_result
  entries across turns), runs the same agent loop as the Ollama
  path — repeat-failure detection, manual approval via
  `requestToolApproval`, `aiSettings.alwaysAllowTools`,
  `MAX_ITERATIONS = aiSettings.deepMode ? 15 : 5`, planner-mode
  gating left intact for the Ollama path. Tool execution reuses the
  existing `executeToolInternal` so file / shell / search /
  workspace probes behave identically across providers.
- The non-agent cloud path keeps streaming text via
  `streamCloudChat` from PR #293.

Tests
- 17 unit tests for `cloudTools.ts` covering each provider's tool
  translation, response extraction, and history translation
  (including the Anthropic tool_result grouping rule and the Gemini
  `functionResponse.response` object-wrapping fallback).

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 (was 125 — added 17 cloudTools tests).
- pnpm build (Next 16 export) clean.
- cargo build / clippy / fmt --check clean. cargo test --lib: 112 / 112.

* feat: detach bottom panel into a floating window (#296)

PR #282 added detach for right-panel views. Bottom panel was the next
deferred item. Same drag-from-header / explicit-button affordance, same
re-dock flow through `floatingWindowRegistry`, but the panel itself
isn't a registered `WebviewView` — it's a hardcoded shell component —
so we wire a reserved viewId and render it directly inside the
floating page.

Registry
- `DetachablePanel` now accepts `"bottom"` alongside `"right"` / `"left"`.
- New `BOTTOM_PANEL_VIEW_ID` constant (`trixty.builtin.bottom-panel`)
  exported from the registry. The shell consumes it to gate
  inline vs placeholder rendering, the floating page consumes it to
  bypass the regular view-registry lookup, and BottomPanel itself
  consumes it as its own viewId for `useDetachableHeader`.

Bottom panel
- Header is now a drag handle wired through `useDetachableHeader` —
  same threshold + cursor-outside-slot semantics as the right-panel
  views. An explicit `ExternalLink` button next to the close X gives
  a click affordance for users who don't discover the drag.
- New `isFloating` prop (default `false`). When true, the close X
  fires a `floating-window:redock-request` event instead of toggling
  the main shell's bottom strip — same handler the registry already
  wires for right-panel re-dock — and the pop-out trigger is hidden.
- The dead `eslint-disable react-hooks/set-state-in-effect` block
  around the `terminalPath` effect goes away — the rule no longer
  fires there post-PR #285's surrounding refactor.

Floating page
- `viewId === BOTTOM_PANEL_VIEW_ID` short-circuits the regular
  `useRegisteredView` path and renders `<BottomPanel isFloating />`
  inside the same `FloatingTitleBar` shell the other views use.
  Title resolves from `panel.bottom.terminal_tabs`; icon stays
  consistent with the inline header.

Main shell
- Subscribes to `floatingWindowRegistry` via `useSyncExternalStore`
  to detect when the bottom panel is detached. The
  `<ResizablePanel id="bottom">` slot stays mounted (so the layout
  preset / resize history doesn't change shape mid-detach) and we
  swap its body to a `BottomPanelDetachedPlaceholder`. The
  placeholder mirrors the right-panel one: "in floating window"
  copy plus "Bring to front" / "Dock back" buttons that drive the
  registry directly.

Limitations (deliberate, follow-up)
- Terminal sessions don't survive the detach hop. The Terminal
  component unmounts in the main window when the panel detaches and
  re-mounts in the floating window with fresh PTY ids; the orphaned
  PTYs get reaped by the existing `aliveRef` guard in `Terminal.tsx`.
  Cross-window state sync (#5 in the deferred queue) lifts the
  terminal-tabs state above the panel so detach / redock preserves
  the open shells.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build (Next 16 export): /, /_not-found, /floating prerender clean.
- Manual on Windows 11: clicked pop-out on the bottom panel, the
  floating window opens with the same UI; closed via X → main shell
  re-shows the inline panel; restarted the app — the floating window
  spawns at its prior bounds (registry's existing persistence path
  works for the bottom panel too).

* feat: cross-window chat history sync between main and floating windows (#297)

Each Tauri WebviewWindow runs its own JS realm, so detaching the AI
chat panel into a floating window today gave you two independent
\`ChatContext\` instances — sending a message in the float left the
main shell stuck on the prior conversation, and switching sessions
in either window had no effect on the other. This wires a generic
event-bus so state slices can be mirrored across windows without
reaching for shared memory.

Sync layer
- New \`src/api/crossWindowSync.ts\` exposes a tiny pub-sub layer over
  Tauri events. \`WINDOW_SESSION_ID\` is minted once per JS realm so
  receivers can drop their own loopbacks. \`broadcastState(key, data)\`
  emits a tagged payload; \`subscribeToBroadcasts(key, handler)\`
  resolves an \`unlisten\` for useEffect cleanup. Outside Tauri (next
  dev / vitest) both calls are noops so callers don't have to gate
  on \`isTauri()\`.

Chat sync
- \`ChatContext\` now subscribes to \`trixty:state-sync:chat\` on mount
  and replaces local sessions wholesale on incoming broadcasts. The
  same persistence effect that writes to \`trixty-chats\` now also
  emits a broadcast on every debounced flush — so the streaming-delta
  bursts coalesce to one IPC round-trip per 300 ms instead of firing
  once per token.
- \`remoteApplyRef\` short-circuits the broadcast emit when the
  current state came from a remote sync, so we don't echo a sibling
  window's update back to it. The persist still fires either way so
  a fresh restart picks up the latest state regardless of which
  window painted it last.

Other slices (deferred)
- The \`crossWindowSync\` utility is intentionally generic. Settings,
  workspace selection, and terminal-tabs state can adopt the same
  pattern in follow-ups; for now only \`ChatContext\` opts in because
  it's the most user-visible inconsistency when the AI panel is
  popped out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: detached the AI chat panel, sent a message in
  the float — the main shell's chat list updated within one debounce
  tick. Switched sessions in main → float caught up on next tick.
  Re-docked → no duplicate or stale messages remain.

* feat: JSON Crack-style graph view for .json files (#298)

PR #285 shipped Tree and Form visual surfaces for JSON / package.json
files. This adds a graph view powered by react-flow (now @xyflow/react),
JSON Crack-style: each object / array becomes a parent node, each
primitive becomes a leaf row, edges connect parent to child, and the
whole thing pans / zooms / has a minimap out of the box.

Visual surface
- New `JsonGraphEditor.tsx` walks the parsed JSON recursively to build
  react-flow nodes + edges. Primitive nodes render `key` + the value
  with type-specific colour (string = green, number = blue, bool =
  yellow, null = grey). Container nodes render `key` + a `[ N items ]`
  / `{ N keys }` summary. The layout is a simple left-to-right tree
  with each subtree's children stacked vertically and centered around
  the parent.
- 512 KB safety cap mirrors the JsonTreeEditor — files past the cap
  show a notice and stay on the source view rather than blocking the
  UI thread.
- Read-only by design. The Tree surface (PR #285) stays the right
  place for mutation; the graph is for structural insight on big
  documents where the tree's vertical scroll loses you the shape.

Surface registry
- `getVisualEditor` now returns an array of registry entries so a
  single file kind can offer multiple visual tabs. Generic `.json`
  registers Tree + Graph; `package.json` registers Form + Graph;
  `.env` keeps Table only. Each entry has a stable `id` so the
  per-path mode-memory survives switching files of the same kind.
- `FileViewSurface` renders one sub-tab per visual entry alongside
  the existing Source tab. Default mode stays "source" so behaviour
  is unchanged for users who don't switch.

Dependencies
- `@xyflow/react ^12.10.2` added to `apps/desktop/package.json`.
  Same package family that powers JSON Crack, n8n, Reactflow.dev's
  own demos. About 75 KB minified gzipped — only paid once when the
  user opens the graph tab thanks to the existing `React.lazy`
  registration in `getVisualEditor`.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean — the lazy
  import keeps `@xyflow/react` off the boot graph for users who never
  open a JSON file.

* feat: drag-to-reorder rows in .env and package.json visual editors (#299)

Issue #264 shipped tabular surfaces for .env and package.json. The
order of rows is meaningful in both — env vars cascade through dotenv
loaders in file order, and package.json scripts / deps are usually
ordered to make the file readable. Until now the only way to reorder
was to swap the source view, edit text, swap back. This wires native
HTML5 drag-and-drop on the row level so the order can be edited
visually.

Hook
- New `src/hooks/useDragReorder.ts` exposes a generic
  `useDragReorder({ items, getId, onReorder, groupKey? })` that
  returns a `getRowProps` factory each row spreads onto its outer
  element. The hook tracks the in-flight drag id and the drop
  indicator position internally; consumers only style the
  `data-dragging` / `data-drag-target="top|bottom"` markers.
- Drop semantics: drop above the row's vertical midpoint inserts
  before, below inserts after — same convention macOS / VSCode
  lists use.
- Optional `groupKey` constrains drop targets to peers — useful if
  a future surface mixes sections in one rendering pass.

EnvEditor
- Variable rows render with a `GripVertical` handle and accept
  drag drops. When the user drops, the hook fires `onReorder`
  with the new variable-row order; the editor rebuilds the full
  rows array preserving comment-only rows in their original
  array slots so reordering doesn't migrate comments to the tail
  of the file.

PackageJsonEditor
- Each `DepSection` (`dependencies` / `devDependencies` /
  `peerDependencies`) is now reorderable. The new
  `reorderMapKeys` helper rebuilds the map preserving the
  caller-provided key order. JS keeps insertion order for string
  keys, so `JSON.stringify` writes the new order to disk
  exactly. Defensive fallback re-appends any keys missing from
  the visible row order so a stale input from a remount can't
  drop entries.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: opened `apps/desktop/.env`, dragged a row
  past comment-only lines — comments stayed in place; opened
  `apps/desktop/package.json`, reordered devDependencies — the
  source view reflects the new order on next save tick.

* feat: open another workspace in a new window via Ctrl+Shift+N (#300)

Two repos side-by-side without context-switching. Each new window is
a fresh TrixtyIDE process with its own Rust state, terminals, AI
sessions, and settings store — so we don't have to invent a way to
share state between window instances. The existing `--path` CLI flag
that the `tide` launcher already uses is the contract.

Rust
- New `spawn_workspace_instance(path)` Tauri command resolves
  `current_exe()`, canonicalises the user-supplied folder, and
  spawns a detached `<binary> --path <folder>` process. Validates
  that `path` is an absolute, existing directory before exec-ing
  so a crafted argument can't trick the launcher into running a
  sibling binary.
- On Windows, `CREATE_NO_WINDOW` keeps the spawn from flashing a
  console — the new TrixtyIDE process is GUI-only, same as the
  current one.

UI
- New `Ctrl+Shift+N` shortcut in the main shell. Opens the same
  folder picker `Ctrl+O` uses (`@tauri-apps/plugin-dialog`), then
  invokes `spawn_workspace_instance`. Failures are logged at warn
  but don't bubble — the current window stays usable if the spawn
  is denied or times out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check / test --lib
  (112) clean.
- Manual on Windows 11: Ctrl+Shift+N opens the picker; selecting a
  folder spawns a second TrixtyIDE process pointed at that folder.
  Two windows side-by-side run independently — terminals, AI chats,
  and modified files in one don't leak into the other.

* Sentry telemetry (#292)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

* ci: add GitHub Actions workflow to automate Tauri build testing on Windows

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* fix(desktop): avoid Tauri state TypeId collision between Ollama and Cloud streams (#301)

`OllamaStreams` and `CloudStreams` were both type aliases of
`Arc<Mutex<HashMap<String, oneshot::Sender<()>>>>`. Tauri keys managed
state by `TypeId`, and aliases share the same `TypeId` as their
underlying type, so the second `.manage()` call panicked at startup
with "state for type ... is already being managed".

Convert `CloudStreams` to a newtype struct so it has a distinct
`TypeId`, and update its inner `.lock()` call sites accordingly.

* fix(desktop): use crypto.getRandomValues fallback for WINDOW_SESSION_ID (#302)

The runtime fallback used Math.random, which CodeQL flags as
js/insecure-randomness (CWE-338). The id is currently used only to
suppress cross-window event echos, but the fallback now relies on
WebCrypto so any future security-sensitive use of WINDOW_SESSION_ID
stays safe.

Order of preference:
  1. crypto.randomUUID         — modern Tauri webview, Node 19+, browsers
  2. crypto.getRandomValues    — older jsdom / runtimes without randomUUID
  3. Date.now + performance.now — last-resort, only echo-suppression

* feat: implement core UI components, localization hooks, and agent configuration settings for the Trixty IDE desktop application.

* Improvement: Add Discord RPC

* add: Included colavorative features using discord (beta)

* feat: implement agent architecture with context providers, workspace synchronization, and Discord RPC integration

* feat: implement StatusBar and TitleBar components with integrated collaboration state and layout controls

* feat: implement CollaborationContext for Yjs-based real-time sessions with Discord RPC integration

* feat: implement CollaborationContext for Yjs-based real-time syncing and Discord RPC join requests

* feat: implement Status Bar and Tab Bar components with collaboration status, file metadata, and keyboard-navigable tab management

* feat: add real-time collaboration support via Yjs and WebRTC with status bar integration

* chore: increment version

* fix: fixed wrong version

* feat: implement backend support for workspace synchronization, Discord RPC, and dynamic update channels

* fixed lint errors in quality check

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* feat: implement cross-platform Discord Rich Presence IPC integration

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* feat: integrate a internal browser for localhost (#310)

* feat: integrate a internal browser for localhost

* Potential fix for pull request finding 'CodeQL / DOM text reinterpreted as HTML'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Potential fix for pull request finding 'CodeQL / DOM text reinterpreted as HTML'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* feat: initialize Tauri desktop application configuration and dependency manifest

* feat: implement BrowserView component with configurable port connectivity and status monitoring

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* chore(bump version): bump version

* fix: fixed fmt error

* fixed fmt again

* Fix csp problem (#317)

* fixed fmt error (#313)

* Main (#309)

* fixed lint errors (#308)

* feat: add Sentry error tracking (#287)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Revert "feat: add Sentry error tracking (#287)" (#291)

This reverts commit c779d520828022c1e7ccca6b808ed44a25325814.

* feat: cloud AI streaming for OpenAI / Anthropic / Gemini / OpenRouter (#293)

PR #284 shipped cloud chat as a single-shot non-streaming bridge —
every reply landed atomically once the request finished. That made the
cloud branch feel slower than Ollama, where the chat panel renders
tokens as they arrive. This wires SSE streaming through a new
`cloud_proxy_stream` Tauri command so the UX matches.

Rust
- New `cloud_proxy_stream` and `cloud_proxy_cancel` commands behind the
  existing `validate_cloud_proxy_request` policy. The command spawns a
  detached tokio task, registers a oneshot cancel handle keyed by the
  caller-supplied `streamId`, and emits `cloud-stream` events as
  `{ kind: "data" | "done" | "error", data?, error? }`.
- New `split_sse_events` parser splits the response buffer on
  `\n\n` / `\r\n\r\n`, joins multi-`data:` lines per event, and drops
  `event:` / `id:` / `retry:` / comment lines. The Rust side stays
  provider-agnostic; the per-provider JSON shape is parsed in TS.
- The `data: [DONE]` sentinel that OpenAI / OpenRouter use is collapsed
  to a structured `done` event so callers don't have to special-case it.
  Anthropic and Gemini close the connection silently on completion;
  the task synthesises a `done` event on EOF so the awaiter resolves
  uniformly.
- Total stream size capped by the same `CLOUD_PROXY_MAX_BODY` limit
  (16 MiB) that `cloud_proxy` uses, and per-request timeout uses
  `CLOUD_PROXY_TIMEOUT_SECS` (60 s). Cancellation tears the task down
  via the oneshot.
- 10 unit tests for `split_sse_events` covering single events, multi-line
  data joining, CRLF separators, partial-event buffering, the [DONE]
  sentinel, and the SSE-spec single-leading-space rule.

TypeScript
- New `streamCloudChat(req, onDelta)` mirroring `streamOllamaChat`'s
  shape: subscribes to `cloud-stream`, filters by `streamId`, parses
  the provider-specific delta, and resolves with the full text on done.
- Provider-specific delta extractors split into
  `extractStreamDelta(provider, raw)`:
  - OpenAI / OpenRouter: `choices[0].delta.content`
  - Anthropic: `content_block_delta` events with `delta.type === "text_delta"`
    only — `message_start`, `content_block_start`, `ping`, `message_stop`,
    `input_json_delta` (future tool deltas) are ignored
  - Gemini: `candidates[*].content.parts[*].text` joined
- The matching `extractFullResponse` is exported and unit-tested so the
  non-streaming `cloudChat` shares the same parsing surface.
- The provider URL / headers / body builder is consolidated into
  `buildProviderRequest(req, stream)`. Gemini's URL flips between
  `:generateContent` and `:streamGenerateContent?alt=sse`; the others
  just toggle `stream` in the body.
- 12 unit tests covering each provider's streaming and full-response
  branches, including the role-only chunk that OpenAI sends as the
  first delta and Anthropic's housekeeping events.

UI wiring
- `AiChatComponent` cloud branch now uses `streamCloudChat` with the
  same `placeholderPushed` / `appendToLastAiMessage` pattern the Ollama
  branch uses. If the stream fails before any content arrives we still
  render a clean error bubble; if it fails mid-stream we append the
  error inline so the user sees how far the model got.
- Tools / agent mode for cloud providers stays out of scope. Each
  provider has its own tool-call shape (`tool_calls` for OpenAI,
  `tool_use` blocks for Anthropic, `functionDeclarations` for Gemini)
  and that lands in the next PR.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 (was 113 — added 12 provider tests).
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 (was 102 — added 10 SSE-parser tests).

* feat: store cloud AI provider keys in OS keychain (#294)

PR #284 shipped multi-provider keys behind a master switch and PR #286
added a banner warning that those keys lived in `settings.json`
plaintext. This wires the OS native secret store (review item I1) so
the keys never touch disk in clear text.

Backing store
- macOS: Keychain
- Windows: Credential Manager
- Linux: Secret Service / kwallet

The `keyring` crate (3.x) handles per-platform shimming. Pinned to 3.x
because keyring 4.x pulls `turso` (an embedded SQLite engine) and
`bon-macros` as transitive deps for its DB-backed unified store, none
of which we use; our `Entry::new` flow only needs the native OS
keychain backends that 3.x exposes by default.

Rust
- Four new commands behind a hard-coded provider allow-list
  (`openai` / `anthropic` / `gemini` / `openrouter`):
  `set_provider_secret`, `get_provider_secret`, `clear_provider_secret`,
  `has_provider_secret`. Service name is fixed at `trixty.ide` so
  entries are namespaced to the app and not visible to other tooling.
- `validate_secret_provider` rejects any string outside the allow-list
  so a renderer XSS can't probe arbitrary keychain entries.
- `keyring::Error::NoEntry` is mapped to `Ok(None)` / `Ok(false)` /
  `Ok(())` (idempotent clear), so the UI's "never configured" /
  "remove" paths don't surface false-positive error toasts.

TypeScript
- New `src/api/providerSecrets.ts` thin wrapper exposing
  `setProviderSecret` / `getProviderSecret` / `hasProviderSecret` /
  `clearProviderSecret` plus the `SECRET_PROVIDERS` list. Empty
  strings round-trip as "no key" — `setProviderSecret(p, "")` clears
  the entry, mirroring the way the old plaintext field treated `""`.
- Tauri-binding map extended with the four commands.

UI
- `ProviderKeysPanel` now loads each provider's key from the keychain
  on mount, holds it in component state for the reveal toggle and
  edit flow, and persists changes via a 500 ms debounced
  `setProviderSecret` (also flushed on blur). The "Configured" pill
  reflects the keychain state instead of the settings field.
- Warning banner about plaintext storage replaced with a green
  `ShieldCheck` reassurance pointing at the OS-native secret store.
- `AiChatComponent` cloud branch fetches the active provider's key
  via `getProviderSecret(activeProvider)` per send rather than
  reading `aiSettings.providerKeys`. Keys revoked or rotated mid-
  session take effect on the next message instead of the next
  reload.
- `keyForProvider` and the `ProviderKeys` import are removed from
  `client.ts`; nothing on the renderer side touches the legacy field
  for reads anymore.

Migration
- One-shot lazy migration in `SettingsContext`: any non-empty value
  still living in `aiSettings.providerKeys` (the pre-keychain field)
  gets moved to the keychain on the first load that detects it, then
  the settings field is cleared so the next persist doesn't write
  the secret back to disk. Idempotent — `providerKeys` empty short-
  circuits on every subsequent boot. Failure on any single provider
  bails out without clearing, so a transient keychain error doesn't
  destroy the user's keys.

Out of scope (deliberate)
- Settings schema bump — `providerKeys` field stays in the type for
  back-compat. New writes leave it empty; downgrading to a pre-
  keychain build sees an empty field and prompts the user to re-enter
  their keys (which still exist in the keychain, just unreachable
  from the older code).
- macOS Touch ID prompt suppression. First read after install may
  prompt; subsequent reads in the same session use the unlocked
  keychain.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 125 / 125 passing.
- pnpm build (Next 16 export) clean.
- cargo build / clippy --lib -- -D warnings / fmt --check clean.
- cargo test --lib: 112 / 112 passing.
- Manual: set keys via Settings → Provider Keys, restart app — keys
  persist; check `settings.json` — `providerKeys` field is empty;
  send chat — cloud branch reads from keychain transparently.

* feat: cloud AI tool calling and agent mode for OpenAI / Anthropic / Gemini / OpenRouter (#295)

PR #284 shipped multi-provider cloud chat as text-only. PR #293 added
streaming. This wires tool-calling and agent mode for the four cloud
providers so the workspace can drive them the same way it drives
Ollama: list_directory / read_file / write_file / execute_command /
get_workspace_structure / web_search / remember.

Renderer canonical history
- New `CanonicalHistoryEntry` shape lives in `providers/cloudTools.ts`
  alongside `ToolDefinition` and `UnifiedToolCall`. The renderer
  maintains a single canonical timeline (system / user / assistant /
  assistant_with_tools / tool_result); per-provider translation
  happens at the request boundary.
- `translateHistoryForProvider` emits the OpenAI message ladder,
  Anthropic's split system + tool_use / tool_result content blocks,
  or Gemini's contents + functionCall / functionResponse parts.
- `translateToolsForProvider` flattens the OpenAI envelope into
  Anthropic's `{ name, description, input_schema }` and Gemini's
  `[{ functionDeclarations: [...] }]`. OpenAI / OpenRouter pass
  through unchanged.
- `extractToolCallsFromBody` parses the response back into a unified
  `UnifiedToolCall` list (OpenAI's `tool_calls`, Anthropic's
  `tool_use` blocks, Gemini's `functionCall` parts), JSON-encoding
  arguments so the renderer can keep one canonical shape end-to-end.

Per-provider request
- New `cloudAgentChat(req)` in `providers/client.ts` — single-shot
  per turn. Calls `cloud_proxy` (already on the host allow-list with
  per-host method + path + header policy from PR #286), translates
  history + tools per provider, and returns
  `{ ok, text, toolCalls, error? }`.
- Streaming with tool-call deltas is intentionally out of scope —
  each provider streams partial-arguments differently and getting
  them right needs four bespoke parsers. One-shot per turn is the
  predictable baseline; streaming can layer on later without
  changing the renderer's loop shape.

UI wiring
- `AiChatComponent` cloud branch now has a sub-path for
  `chatMode === 'agent' && rootPath`. Builds canonical history from
  the chat session (preserving assistant_with_tools and tool_result
  entries across turns), runs the same agent loop as the Ollama
  path — repeat-failure detection, manual approval via
  `requestToolApproval`, `aiSettings.alwaysAllowTools`,
  `MAX_ITERATIONS = aiSettings.deepMode ? 15 : 5`, planner-mode
  gating left intact for the Ollama path. Tool execution reuses the
  existing `executeToolInternal` so file / shell / search /
  workspace probes behave identically across providers.
- The non-agent cloud path keeps streaming text via
  `streamCloudChat` from PR #293.

Tests
- 17 unit tests for `cloudTools.ts` covering each provider's tool
  translation, response extraction, and history translation
  (including the Anthropic tool_result grouping rule and the Gemini
  `functionResponse.response` object-wrapping fallback).

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 (was 125 — added 17 cloudTools tests).
- pnpm build (Next 16 export) clean.
- cargo build / clippy / fmt --check clean. cargo test --lib: 112 / 112.

* feat: detach bottom panel into a floating window (#296)

PR #282 added detach for right-panel views. Bottom panel was the next
deferred item. Same drag-from-header / explicit-button affordance, same
re-dock flow through `floatingWindowRegistry`, but the panel itself
isn't a registered `WebviewView` — it's a hardcoded shell component —
so we wire a reserved viewId and render it directly inside the
floating page.

Registry
- `DetachablePanel` now accepts `"bottom"` alongside `"right"` / `"left"`.
- New `BOTTOM_PANEL_VIEW_ID` constant (`trixty.builtin.bottom-panel`)
  exported from the registry. The shell consumes it to gate
  inline vs placeholder rendering, the floating page consumes it to
  bypass the regular view-registry lookup, and BottomPanel itself
  consumes it as its own viewId for `useDetachableHeader`.

Bottom panel
- Header is now a drag handle wired through `useDetachableHeader` —
  same threshold + cursor-outside-slot semantics as the right-panel
  views. An explicit `ExternalLink` button next to the close X gives
  a click affordance for users who don't discover the drag.
- New `isFloating` prop (default `false`). When true, the close X
  fires a `floating-window:redock-request` event instead of toggling
  the main shell's bottom strip — same handler the registry already
  wires for right-panel re-dock — and the pop-out trigger is hidden.
- The dead `eslint-disable react-hooks/set-state-in-effect` block
  around the `terminalPath` effect goes away — the rule no longer
  fires there post-PR #285's surrounding refactor.

Floating page
- `viewId === BOTTOM_PANEL_VIEW_ID` short-circuits the regular
  `useRegisteredView` path and renders `<BottomPanel isFloating />`
  inside the same `FloatingTitleBar` shell the other views use.
  Title resolves from `panel.bottom.terminal_tabs`; icon stays
  consistent with the inline header.

Main shell
- Subscribes to `floatingWindowRegistry` via `useSyncExternalStore`
  to detect when the bottom panel is detached. The
  `<ResizablePanel id="bottom">` slot stays mounted (so the layout
  preset / resize history doesn't change shape mid-detach) and we
  swap its body to a `BottomPanelDetachedPlaceholder`. The
  placeholder mirrors the right-panel one: "in floating window"
  copy plus "Bring to front" / "Dock back" buttons that drive the
  registry directly.

Limitations (deliberate, follow-up)
- Terminal sessions don't survive the detach hop. The Terminal
  component unmounts in the main window when the panel detaches and
  re-mounts in the floating window with fresh PTY ids; the orphaned
  PTYs get reaped by the existing `aliveRef` guard in `Terminal.tsx`.
  Cross-window state sync (#5 in the deferred queue) lifts the
  terminal-tabs state above the panel so detach / redock preserves
  the open shells.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build (Next 16 export): /, /_not-found, /floating prerender clean.
- Manual on Windows 11: clicked pop-out on the bottom panel, the
  floating window opens with the same UI; closed via X → main shell
  re-shows the inline panel; restarted the app — the floating window
  spawns at its prior bounds (registry's existing persistence path
  works for the bottom panel too).

* feat: cross-window chat history sync between main and floating windows (#297)

Each Tauri WebviewWindow runs its own JS realm, so detaching the AI
chat panel into a floating window today gave you two independent
\`ChatContext\` instances — sending a message in the float left the
main shell stuck on the prior conversation, and switching sessions
in either window had no effect on the other. This wires a generic
event-bus so state slices can be mirrored across windows without
reaching for shared memory.

Sync layer
- New \`src/api/crossWindowSync.ts\` exposes a tiny pub-sub layer over
  Tauri events. \`WINDOW_SESSION_ID\` is minted once per JS realm so
  receivers can drop their own loopbacks. \`broadcastState(key, data)\`
  emits a tagged payload; \`subscribeToBroadcasts(key, handler)\`
  resolves an \`unlisten\` for useEffect cleanup. Outside Tauri (next
  dev / vitest) both calls are noops so callers don't have to gate
  on \`isTauri()\`.

Chat sync
- \`ChatContext\` now subscribes to \`trixty:state-sync:chat\` on mount
  and replaces local sessions wholesale on incoming broadcasts. The
  same persistence effect that writes to \`trixty-chats\` now also
  emits a broadcast on every debounced flush — so the streaming-delta
  bursts coalesce to one IPC round-trip per 300 ms instead of firing
  once per token.
- \`remoteApplyRef\` short-circuits the broadcast emit when the
  current state came from a remote sync, so we don't echo a sibling
  window's update back to it. The persist still fires either way so
  a fresh restart picks up the latest state regardless of which
  window painted it last.

Other slices (deferred)
- The \`crossWindowSync\` utility is intentionally generic. Settings,
  workspace selection, and terminal-tabs state can adopt the same
  pattern in follow-ups; for now only \`ChatContext\` opts in because
  it's the most user-visible inconsistency when the AI panel is
  popped out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: detached the AI chat panel, sent a message in
  the float — the main shell's chat list updated within one debounce
  tick. Switched sessions in main → float caught up on next tick.
  Re-docked → no duplicate or stale messages remain.

* feat: JSON Crack-style graph view for .json files (#298)

PR #285 shipped Tree and Form visual surfaces for JSON / package.json
files. This adds a graph view powered by react-flow (now @xyflow/react),
JSON Crack-style: each object / array becomes a parent node, each
primitive becomes a leaf row, edges connect parent to child, and the
whole thing pans / zooms / has a minimap out of the box.

Visual surface
- New `JsonGraphEditor.tsx` walks the parsed JSON recursively to build
  react-flow nodes + edges. Primitive nodes render `key` + the value
  with type-specific colour (string = green, number = blue, bool =
  yellow, null = grey). Container nodes render `key` + a `[ N items ]`
  / `{ N keys }` summary. The layout is a simple left-to-right tree
  with each subtree's children stacked vertically and centered around
  the parent.
- 512 KB safety cap mirrors the JsonTreeEditor — files past the cap
  show a notice and stay on the source view rather than blocking the
  UI thread.
- Read-only by design. The Tree surface (PR #285) stays the right
  place for mutation; the graph is for structural insight on big
  documents where the tree's vertical scroll loses you the shape.

Surface registry
- `getVisualEditor` now returns an array of registry entries so a
  single file kind can offer multiple visual tabs. Generic `.json`
  registers Tree + Graph; `package.json` registers Form + Graph;
  `.env` keeps Table only. Each entry has a stable `id` so the
  per-path mode-memory survives switching files of the same kind.
- `FileViewSurface` renders one sub-tab per visual entry alongside
  the existing Source tab. Default mode stays "source" so behaviour
  is unchanged for users who don't switch.

Dependencies
- `@xyflow/react ^12.10.2` added to `apps/desktop/package.json`.
  Same package family that powers JSON Crack, n8n, Reactflow.dev's
  own demos. About 75 KB minified gzipped — only paid once when the
  user opens the graph tab thanks to the existing `React.lazy`
  registration in `getVisualEditor`.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean — the lazy
  import keeps `@xyflow/react` off the boot graph for users who never
  open a JSON file.

* feat: drag-to-reorder rows in .env and package.json visual editors (#299)

Issue #264 shipped tabular surfaces for .env and package.json. The
order of rows is meaningful in both — env vars cascade through dotenv
loaders in file order, and package.json scripts / deps are usually
ordered to make the file readable. Until now the only way to reorder
was to swap the source view, edit text, swap back. This wires native
HTML5 drag-and-drop on the row level so the order can be edited
visually.

Hook
- New `src/hooks/useDragReorder.ts` exposes a generic
  `useDragReorder({ items, getId, onReorder, groupKey? })` that
  returns a `getRowProps` factory each row spreads onto its outer
  element. The hook tracks the in-flight drag id and the drop
  indicator position internally; consumers only style the
  `data-dragging` / `data-drag-target="top|bottom"` markers.
- Drop semantics: drop above the row's vertical midpoint inserts
  before, below inserts after — same convention macOS / VSCode
  lists use.
- Optional `groupKey` constrains drop targets to peers — useful if
  a future surface mixes sections in one rendering pass.

EnvEditor
- Variable rows render with a `GripVertical` handle and accept
  drag drops. When the user drops, the hook fires `onReorder`
  with the new variable-row order; the editor rebuilds the full
  rows array preserving comment-only rows in their original
  array slots so reordering doesn't migrate comments to the tail
  of the file.

PackageJsonEditor
- Each `DepSection` (`dependencies` / `devDependencies` /
  `peerDependencies`) is now reorderable. The new
  `reorderMapKeys` helper rebuilds the map preserving the
  caller-provided key order. JS keeps insertion order for string
  keys, so `JSON.stringify` writes the new order to disk
  exactly. Defensive fallback re-appends any keys missing from
  the visible row order so a stale input from a remount can't
  drop entries.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137 passing.
- pnpm build: /, /_not-found, /floating prerender clean.
- Manual on Windows 11: opened `apps/desktop/.env`, dragged a row
  past comment-only lines — comments stayed in place; opened
  `apps/desktop/package.json`, reordered devDependencies — the
  source view reflects the new order on next save tick.

* feat: open another workspace in a new window via Ctrl+Shift+N (#300)

Two repos side-by-side without context-switching. Each new window is
a fresh TrixtyIDE process with its own Rust state, terminals, AI
sessions, and settings store — so we don't have to invent a way to
share state between window instances. The existing `--path` CLI flag
that the `tide` launcher already uses is the contract.

Rust
- New `spawn_workspace_instance(path)` Tauri command resolves
  `current_exe()`, canonicalises the user-supplied folder, and
  spawns a detached `<binary> --path <folder>` process. Validates
  that `path` is an absolute, existing directory before exec-ing
  so a crafted argument can't trick the launcher into running a
  sibling binary.
- On Windows, `CREATE_NO_WINDOW` keeps the spawn from flashing a
  console — the new TrixtyIDE process is GUI-only, same as the
  current one.

UI
- New `Ctrl+Shift+N` shortcut in the main shell. Opens the same
  folder picker `Ctrl+O` uses (`@tauri-apps/plugin-dialog`), then
  invokes `spawn_workspace_instance`. Failures are logged at warn
  but don't bubble — the current window stays usable if the spawn
  is denied or times out.

Verification
- pnpm tsc --noEmit / lint --max-warnings 0 clean.
- pnpm vitest run: 137 / 137.
- pnpm build: /, /_not-found, /floating prerender clean.
- cargo build / clippy --lib -- -D warnings / fmt --check / test --lib
  (112) clean.
- Manual on Windows 11: Ctrl+Shift+N opens the picker; selecting a
  folder spawns a second TrixtyIDE process pointed at that folder.
  Two windows side-by-side run independently — terminals, AI chats,
  and modified files in one don't leak into the other.

* Sentry telemetry (#292)

* feat: add Sentry error tracking

* feat: integrate Sentry error tracking and add marketplace registry and settings UI enhancements

* improvement: add sentry

* fix: fix lib conflict

* Potential fix for pull request finding 'Useless conditional'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* ci: add GitHub Actions workflows for automated test builds and multi-platform releases

* ci: add GitHub Actions workflow to automate Tauri build testing on Windows

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* fix(desktop): avoid Tauri state TypeId collision between Ollama and Cloud streams (#301)

`OllamaStreams` and `CloudStreams` were both type aliases of
`Arc<Mutex<HashMap<String, oneshot::Sender<()>>>>`. Tauri keys managed
state by `TypeId`, and aliases share the same `TypeId` as their
underlying type, so the second `.manage()` call panicked at startup
with "state for type ... is already being managed".

Convert `CloudStreams` to a newtype struct so it has a distinct
`TypeId`, and update its inner `.lock()` call sites accordingly.

* fix(desktop): use crypto.getRandomValues fallback for WINDOW_SESSION_ID (#302)

The runtime fallback used Math.random, which CodeQL flags as
js/insecure-randomness (CWE-338). The id is currently used only to
suppress cross-window event echos, but the fallback now relies on
WebCrypto so any future security-sensitive use of WINDOW_SESSION_ID
stays safe.

Order of preference:
  1. crypto.randomUUID         — modern Tauri webview, Node 19+, browsers
  2. crypto.getRandomValues    — older jsdom / runtimes without randomUUID
  3. Date.now + performance.now — last-resort, only echo-suppression

* feat: implement core UI components, localization hooks, and agent configuration settings for the Trixty IDE desktop application.

* Improvement: Add Discord RPC

* add: Included colavorative features using discord (beta)

* feat: implement agent architecture with context providers, workspace synchronization, and Discord RPC integration

* feat: implement StatusBar and TitleBar components with integrated collaboration state and layout controls

* feat: implement CollaborationContext for Yjs-based real-time sessions with Discord RPC integration

* feat: implement CollaborationContext for Yjs-based real-time syncing and Discord RPC join requests

* feat: implement Status Bar and Tab Bar components with collaboration status, file metadata, and keyboard-navigable tab management

* feat: add real-time collaboration support via Yjs and WebRTC with status bar integration

* chore: increment version

* fix: fixed wrong version

* feat: implement backend support for workspace synchronization, Discord RPC, and dynamic update channels

* fixed lint errors in quality check

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* feat: implement cross-platform Discord Rich Presence IPC integration

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>

* feat: integrate a internal browser for localhost (#310)

* feat: integrate a internal browser for localhost

* Potential fix for pull request finding 'CodeQL / DOM text reinterpreted as HTML'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Potential fix for pull request finding 'CodeQL / DOM text reinterpreted as HTML'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* feat: initialize Tauri desktop application configuration and dependency manifest

* feat: implement BrowserView component with configurable port connectivity and status monitoring

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* chore(bump version): bump version

* fix: fixed fmt error

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* fixed fmt

* fixed devtool

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Matias Palma <83047050+matiaspalmac@users.noreply.github.com>
Co-authored-by: matiaspalmac <matiaspalma2594@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants