Compare commits
@@ -59,7 +59,10 @@
|
|||||||
"Bash(git checkout -- *)",
|
"Bash(git checkout -- *)",
|
||||||
"Bash(git restore .*)",
|
"Bash(git restore .*)",
|
||||||
"Bash(chmod -R 777 *)",
|
"Bash(chmod -R 777 *)",
|
||||||
"Bash(chmod 777 *)"
|
"Bash(chmod 777 *)",
|
||||||
|
"Bash(git add -A*)",
|
||||||
|
"Bash(git add --all*)",
|
||||||
|
"Bash(git add .)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,16 @@ Follow these conventions whenever you create a commit in this repository. These
|
|||||||
|
|
||||||
## Message style
|
## Message style
|
||||||
|
|
||||||
- **First line:** imperative mood, ≤ 70 characters. Examples: `add sidecar PTY scaffold`, `fix IPC reconnect after app reload`, `update CLI exit-code contract`.
|
This repo uses [Conventional Commits 1.0](https://www.conventionalcommits.org/en/v1.0.0/) (per [D-37](../../../governance/decisions/process.md#d-37)).
|
||||||
|
|
||||||
|
- **First line:** `type(scope): imperative subject`, ≤ 72 characters **including** the prefix. Examples: `feat(settings): add Appearance font picker (T-460)`, `fix(ipc): reconnect after app reload`, `docs(readme): drop brittle version line`.
|
||||||
|
- **Type:** one of `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `chore`. Use `feat`/`fix` for user-visible behavior; `chore` for bookkeeping (`chore(plan)` is the convention for pql ticket housekeeping). Append `!` after the scope for a breaking change (`feat(ipc)!: …`).
|
||||||
|
- **Scope (optional but preferred):** the subsystem the change lives in — `settings`, `vim`, `pty`, `git`, `plan`, etc. Lower-case, no spaces.
|
||||||
|
- **Ticket ref:** keep a trailing `(T-NNN)` on the subject when the work has a ticket — `feat(settings): category rail + navigation (T-447)`.
|
||||||
- **Body (optional):** wrap at ~72 chars. Explain the *why* — the reason this change exists. The diff already shows the *what*; don't restate it in prose.
|
- **Body (optional):** wrap at ~72 chars. Explain the *why* — the reason this change exists. The diff already shows the *what*; don't restate it in prose.
|
||||||
- **No emojis.** Anywhere.
|
- **No emojis.** Anywhere.
|
||||||
- **Don't prefix with types** like `feat:` or `fix:` — this repo isn't Conventional Commits. (The Python-era clide under `legacy/` used Conventional Commits; the Flutter rebuild at the repo root does not.)
|
|
||||||
- **Don't reference the current task or flow** (`for the v2.0 milestone`, `used by the canvas panel`) — that context belongs in the PR description and rots as the repo evolves.
|
- **Don't reference the current task or flow** (`for the v2.0 milestone`, `used by the canvas panel`) — that context belongs in the PR description and rots as the repo evolves.
|
||||||
- **Naming:** the project is `clide`. The Flutter desktop app lives at the repo root; the Go sidecar/CLI binary is `clide`. The supporter project is `pql` (referenced, not part of this repo). The archived Python implementation lives under `legacy/`.
|
- **Naming:** the project is `clide`. The Flutter desktop app lives at the repo root; the `clide` CLI is a thin C client (`native/clide-cli/`). The supporter project is `pql` (referenced, not part of this repo). The archived Python implementation lives under `legacy/`.
|
||||||
|
|
||||||
## Logically-separated commits
|
## Logically-separated commits
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,41 @@ These apply across every reference and every surface:
|
|||||||
- Never use `Material*` or `Cupertino*` widgets or color constants — clide
|
- Never use `Material*` or `Cupertino*` widgets or color constants — clide
|
||||||
is `WidgetsApp` only (D-7).
|
is `WidgetsApp` only (D-7).
|
||||||
- Use `ClideText` for themed text; never bare `Text` in production widgets.
|
- Use `ClideText` for themed text; never bare `Text` in production widgets.
|
||||||
- Typography: `clideFontMono` for code/paths/IDs, `clideFontCaption` for
|
- Typography sizes: `clideFontMono` for code/paths/IDs, `clideFontCaption` for
|
||||||
status/section headers, body inherits from `DefaultTextStyle`.
|
status/section headers, body inherits from `DefaultTextStyle`.
|
||||||
|
- Font *family* comes from the user-selectable facade, not a const: a
|
||||||
|
monospace surface uses `fontFamily: ClideSettings.fonts.monoOf(context)`
|
||||||
|
(and `fontFamilyFallback: clideMonoFamilyFallback`); the UI face is inherited
|
||||||
|
via the root `DefaultTextStyle`, or `ClideSettings.fonts.uiOf(context)` when a
|
||||||
|
widget must set it explicitly. `clideMonoFamily` / `clideUiFamily` are the
|
||||||
|
facade's defaults — don't read them directly in new widgets (D-101). Same
|
||||||
|
facade exposes `ClideSettings.theme.of(context)` and `.i18n.of(context)`.
|
||||||
|
- User-facing strings resolve through the catalog, never a hardcoded literal
|
||||||
|
(D-21/D-102): `ClideSettings.i18n.string(context, 'dotted.key', namespace:
|
||||||
|
<ext id or 'core'>, placeholder: '<English>')` (or `.interpolated` for
|
||||||
|
templated). Add the key→English to `assets/i18n/en_us/<namespace>.json`. The
|
||||||
|
`placeholder` is the English fallback; the extension's own id is its
|
||||||
|
namespace (framework chrome uses `core`). Contribution manifests carry
|
||||||
|
`titleKey`/`labelKey` for the same reason.
|
||||||
|
|
||||||
|
## Localization & string length (D-21/D-102)
|
||||||
|
|
||||||
|
- **Config / layout.** Catalogs are bundled assets at
|
||||||
|
`assets/i18n/<locale>/<namespace>.json` — the locale is a *directory*
|
||||||
|
(`en_us`, `nl_nl`, `nl_be`, `en_eu`, …); a new language is a new folder of the
|
||||||
|
same namespace files. The active language is `app.locale` (Settings →
|
||||||
|
Appearance → Language), applied live by `root_shell` via `i18n.setLocale`;
|
||||||
|
add the `Locale` to `availableLocales` in `main.dart` and a folder under
|
||||||
|
`assets/i18n/`. `en_US` is default; `nl_NL` ships.
|
||||||
|
- **Design for length variation.** Translations are not the same width — Dutch
|
||||||
|
runs ~20% longer than English, German more. So **never hard-size a surface to
|
||||||
|
its English label.** Tight surfaces (status-bar items, chips, buttons, tab
|
||||||
|
titles, menu items) must tolerate ~30% growth: let them wrap, ellipsis, or
|
||||||
|
`Flexible`/`Expanded`, not a fixed width tuned to English. When you add or
|
||||||
|
translate a label, sanity-check the length delta on those tight surfaces (an
|
||||||
|
`*.semantics` label is screen-reader-only, so its length never deforms
|
||||||
|
layout). A quick audit: compare `len(nl)/len(en)` per key and eyeball the
|
||||||
|
short-but-grew cases on real (non-semantics) surfaces.
|
||||||
|
|
||||||
## Conversation-panel cards (T-305)
|
## Conversation-panel cards (T-305)
|
||||||
|
|
||||||
|
|||||||
@@ -112,9 +112,37 @@ tooltip → tooltipBackground / tooltipForeground / tooltipBorder
|
|||||||
dropdown → dropdownBackground / dropdownForeground / dropdownBorder
|
dropdown → dropdownBackground / dropdownForeground / dropdownBorder
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Settings & grouped lists — sectioned cards
|
||||||
|
|
||||||
|
Settings surfaces and any long grouped list (e.g. the Claude config lists)
|
||||||
|
read as **sectioned cards**, not bare rows floating on the panel. Each logical
|
||||||
|
group gets its own card; the small-caps section label (+ optional count) sits
|
||||||
|
just **above** the card.
|
||||||
|
|
||||||
|
```
|
||||||
|
panel bg → panelBackground (#20202C)
|
||||||
|
card surface → surface (#242838) fill + dividerColor/border (1px), ~6px corners
|
||||||
|
section head → sidebarSectionHeader (small-caps), with the count muted to its right
|
||||||
|
control inset → inputs INSIDE a card recede to panelBackground, so they still
|
||||||
|
read as fields against the elevated card
|
||||||
|
```
|
||||||
|
|
||||||
|
- **One card per group** — a settings table, each config list. The card's
|
||||||
|
elevated fill + border do the visual separation; don't rely on spacing alone.
|
||||||
|
- **Field row inside a card:** label (`globalForeground`) + help
|
||||||
|
(`globalTextMuted`) + the control right-aligned, with the per-field scope tag
|
||||||
|
in the far-right column.
|
||||||
|
- **Scroll, don't cram:** when stacked cards exceed the modal/pane viewport, the
|
||||||
|
pane scrolls vertically (sticky header, scrolling body) — prefer that over
|
||||||
|
shrinking content to fit one screen.
|
||||||
|
- Pattern reference: the settings wireframes under
|
||||||
|
`docs/design/wireframes/settings/` (T-302).
|
||||||
|
|
||||||
## Anti-patterns
|
## Anti-patterns
|
||||||
|
|
||||||
- `globalBackground` for panel fill → use `panelBackground`
|
- `globalBackground` for panel fill → use `panelBackground`
|
||||||
|
- Bare settings rows on the panel where a group of them should be one card →
|
||||||
|
see "Settings & grouped lists".
|
||||||
- `listItemHoverBackground` in sidebar → use `sidebarItemHover`
|
- `listItemHoverBackground` in sidebar → use `sidebarItemHover`
|
||||||
- Tab active bg = `panelBackground` → use `panelHeader` (elevated chrome)
|
- Tab active bg = `panelBackground` → use `panelHeader` (elevated chrome)
|
||||||
- Tab active border = `globalFocus` → use `panelActiveBorder`
|
- Tab active border = `globalFocus` → use `panelActiveBorder`
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
# Gitea Actions workflow for clide.
|
|
||||||
#
|
|
||||||
# NOT YET ACTIVATED. Gitea Actions must be enabled in the instance
|
|
||||||
# settings before this runs; until then the file is just a ready-made
|
|
||||||
# pipeline Claude + the user can review.
|
|
||||||
#
|
|
||||||
# When the repo eventually lands on GitHub, copy this file verbatim to
|
|
||||||
# `.github/workflows/test.yml` — Gitea Actions consumes GitHub-Actions
|
|
||||||
# syntax, so no rewrite is needed.
|
|
||||||
|
|
||||||
name: test
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
unit:
|
|
||||||
name: unit + widget + golden + a11y
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with: { channel: stable, cache: true }
|
|
||||||
- run: dart pub get
|
|
||||||
- run: (cd app && flutter pub get)
|
|
||||||
- run: ci/test.sh
|
|
||||||
- run: ci/test_a11y.sh
|
|
||||||
- run: ci/coverage_gate.sh
|
|
||||||
|
|
||||||
integration:
|
|
||||||
name: integration_test (xvfb)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: unit
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with: { channel: stable, cache: true }
|
|
||||||
- run: sudo apt-get update && sudo apt-get install -y xvfb ninja-build libgtk-3-dev
|
|
||||||
- run: dart pub get
|
|
||||||
- run: (cd app && flutter pub get)
|
|
||||||
- uses: coactions/setup-xvfb@v1
|
|
||||||
with: { run: ci/test_integration.sh }
|
|
||||||
|
|
||||||
startup-bundle:
|
|
||||||
name: bundle smoke (xvfb 5s)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: unit
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with: { channel: stable, cache: true }
|
|
||||||
- run: sudo apt-get update && sudo apt-get install -y xvfb ninja-build libgtk-3-dev
|
|
||||||
- run: dart pub get
|
|
||||||
- run: (cd app && flutter pub get)
|
|
||||||
- run: ci/smoke_bundle.sh
|
|
||||||
|
|
||||||
e2e:
|
|
||||||
name: daemon subprocess + web WASM smoke
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: unit
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with: { channel: stable, cache: true }
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with: { node-version: 20 }
|
|
||||||
- run: dart pub get
|
|
||||||
- run: (cd app && flutter pub get)
|
|
||||||
- run: (cd tools/ui && npm install && npx playwright install --with-deps chromium)
|
|
||||||
- run: ci/test_e2e.sh
|
|
||||||
|
|
||||||
docs:
|
|
||||||
name: dart doc (lib API)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with: { channel: stable, cache: true }
|
|
||||||
- run: dart pub get
|
|
||||||
- name: dart doc --validate-links (fail on warning)
|
|
||||||
run: |
|
|
||||||
set -o pipefail
|
|
||||||
dart doc --validate-links 2>&1 | tee dartdoc.log
|
|
||||||
if grep -q "^ warning:" dartdoc.log; then
|
|
||||||
echo "::error::dartdoc emitted warnings — see log above"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: dart-api-docs
|
|
||||||
path: doc/api/
|
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# pql: source .pql/hooks/post-checkout (rebuild pql.db on branch checkout)
|
# pql: source .pql/hooks/post-checkout (rebuild pql.db on branch checkout).
|
||||||
. "$(git rev-parse --show-toplevel)/.pql/hooks/post-checkout"
|
# The pql hook is untracked (a local `pql init` install), so a fresh
|
||||||
|
# `git worktree add` has no .pql/hooks — source it only when present, and
|
||||||
|
# always exit 0: post-checkout is best-effort and must never abort the
|
||||||
|
# checkout / worktree creation.
|
||||||
|
hook="$(git rev-parse --show-toplevel)/.pql/hooks/post-checkout"
|
||||||
|
if [ -f "$hook" ]; then . "$hook"; fi
|
||||||
|
exit 0
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
name: release
|
||||||
|
|
||||||
|
# Build + publish versioned Windows and Linux release bundles when the version
|
||||||
|
# in pubspec.yaml changes on main. The `version` job only proceeds when the
|
||||||
|
# v<version> tag doesn't already exist, so an unrelated pubspec edit is a no-op.
|
||||||
|
#
|
||||||
|
# FIRST CUT — neither build has run in CI yet (Windows has never been built at
|
||||||
|
# all), so expect to iterate on these from the first run's logs. The repo's own
|
||||||
|
# `make` targets are the build contract (gen-build-info + clide-cli + flutter
|
||||||
|
# build, all wired in `make build`).
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths: ['pubspec.yaml']
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write # create the tag + the release
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.v.outputs.version }}
|
||||||
|
fresh: ${{ steps.v.outputs.fresh }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with: { fetch-depth: 0 } # tags, to tell new vs. already-released
|
||||||
|
- id: v
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
version=$(awk -F': *' '/^version:/ {gsub(/[" ]/,"",$2); print $2; exit}' pubspec.yaml)
|
||||||
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||||
|
if git rev-parse "v$version" >/dev/null 2>&1; then
|
||||||
|
echo "fresh=false" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "v$version already tagged — nothing to release."
|
||||||
|
else
|
||||||
|
echo "fresh=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "v$version is new — building."
|
||||||
|
fi
|
||||||
|
|
||||||
|
build-linux:
|
||||||
|
needs: version
|
||||||
|
if: needs.version.outputs.fresh == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with: { channel: stable }
|
||||||
|
- run: sudo apt-get update && sudo apt-get install -y ninja-build libgtk-3-dev
|
||||||
|
- run: make dugite-fetch
|
||||||
|
- run: make build # gen-build-info + clide-cli + flutter build linux
|
||||||
|
- name: package
|
||||||
|
run: tar -C build/linux/x64/release/bundle -czf clide-linux-x64-${{ needs.version.outputs.version }}.tar.gz .
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with: { name: linux, path: clide-linux-x64-*.tar.gz }
|
||||||
|
|
||||||
|
build-windows:
|
||||||
|
needs: version
|
||||||
|
if: needs.version.outputs.fresh == 'true'
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with: { channel: stable }
|
||||||
|
- run: choco install -y make
|
||||||
|
- name: build
|
||||||
|
shell: bash
|
||||||
|
run: make dugite-fetch && make build # MSVC + bash already on windows-latest
|
||||||
|
- name: package
|
||||||
|
shell: pwsh
|
||||||
|
run: Compress-Archive -Path build/windows/x64/runner/Release/* -DestinationPath clide-windows-x64-${{ needs.version.outputs.version }}.zip
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with: { name: windows, path: clide-windows-x64-*.zip }
|
||||||
|
|
||||||
|
publish:
|
||||||
|
needs: [version, build-linux, build-windows]
|
||||||
|
if: needs.version.outputs.fresh == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: changelog notes for this version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
ver="${{ needs.version.outputs.version }}"
|
||||||
|
# Pull the entries under `## [<version>]` — the changelog cut that the
|
||||||
|
# version-bump commit lands per the changelog discipline — as the
|
||||||
|
# release body; fall back to a one-liner if the section is absent.
|
||||||
|
awk -v ver="$ver" '
|
||||||
|
$0 ~ "^## \\[" ver "\\]" {grab=1; next}
|
||||||
|
grab && /^## \[/ {exit}
|
||||||
|
grab {print}
|
||||||
|
' CHANGELOG.md > release-notes.md
|
||||||
|
[ -s release-notes.md ] || echo "Release v$ver." > release-notes.md
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with: { path: dist }
|
||||||
|
- uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: v${{ needs.version.outputs.version }}
|
||||||
|
name: clide v${{ needs.version.outputs.version }}
|
||||||
|
body_path: release-notes.md # the version's CHANGELOG section
|
||||||
|
generate_release_notes: true # + auto commit list appended
|
||||||
|
files: dist/**/* # the built versioned bundles
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
# Linux CI for clide (GitHub Actions). Moved here from .gitea/workflows/ when CI
|
||||||
|
# consolidated onto GitHub (the primary remote); the Gitea secondary has Actions
|
||||||
|
# disabled. Pairs with windows.yml (ConPTY suite on windows-latest) and
|
||||||
|
# release.yml (versioned release bundles).
|
||||||
|
#
|
||||||
|
# Steps go through the make targets (the repo's tooling-discipline rule: the
|
||||||
|
# make layer sets up the environment — gen-build-info etc. — and stays correct
|
||||||
|
# if a wrapped script moves).
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
unit:
|
||||||
|
name: unit + widget + golden + a11y + coverage gate
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with: { channel: stable, cache: true }
|
||||||
|
- run: flutter pub get
|
||||||
|
- name: install pql
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
gh release download --repo postmeridiem/pql --pattern 'pql_*_Linux_x86_64.tar.gz' --output /tmp/pql.tgz
|
||||||
|
tar -xzf /tmp/pql.tgz -C /tmp
|
||||||
|
sudo install -m 0755 /tmp/pql /usr/local/bin/pql
|
||||||
|
pql --version
|
||||||
|
# The pql tests query the repo's vault, but .pql/pql.db is gitignored
|
||||||
|
# (the post-checkout hook rebuilds it from the committed changelog).
|
||||||
|
# A fresh CI checkout has the changelog but no db — materialize it,
|
||||||
|
# and sync decision records from the governance/ DQR markdown tree
|
||||||
|
# (tickets come from the changelog; decisions from `decisions sync`).
|
||||||
|
pql plan import
|
||||||
|
pql decisions sync
|
||||||
|
# test-coverage runs the full fast suite WITH coverage (it includes the
|
||||||
|
# a11y suite — see the push-check note in the Makefile), which is what
|
||||||
|
# coverage-gate consumes.
|
||||||
|
- run: make test-coverage
|
||||||
|
- run: make coverage-gate
|
||||||
|
|
||||||
|
integration:
|
||||||
|
name: integration_test (xvfb)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: unit
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with: { channel: stable, cache: true }
|
||||||
|
- run: sudo apt-get update && sudo apt-get install -y xvfb ninja-build libgtk-3-dev
|
||||||
|
- run: flutter pub get
|
||||||
|
- name: install pql
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
gh release download --repo postmeridiem/pql --pattern 'pql_*_Linux_x86_64.tar.gz' --output /tmp/pql.tgz
|
||||||
|
tar -xzf /tmp/pql.tgz -C /tmp
|
||||||
|
sudo install -m 0755 /tmp/pql /usr/local/bin/pql
|
||||||
|
pql --version
|
||||||
|
# The pql tests query the repo's vault, but .pql/pql.db is gitignored
|
||||||
|
# (the post-checkout hook rebuilds it from the committed changelog).
|
||||||
|
# A fresh CI checkout has the changelog but no db — materialize it,
|
||||||
|
# and sync decision records from the governance/ DQR markdown tree
|
||||||
|
# (tickets come from the changelog; decisions from `decisions sync`).
|
||||||
|
pql plan import
|
||||||
|
pql decisions sync
|
||||||
|
- uses: coactions/setup-xvfb@v1
|
||||||
|
with: { run: make test-integration }
|
||||||
|
|
||||||
|
startup-bundle:
|
||||||
|
name: bundle smoke (xvfb 5s)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: unit
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with: { channel: stable, cache: true }
|
||||||
|
- run: sudo apt-get update && sudo apt-get install -y xvfb ninja-build libgtk-3-dev
|
||||||
|
- run: flutter pub get
|
||||||
|
- name: install pql
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
gh release download --repo postmeridiem/pql --pattern 'pql_*_Linux_x86_64.tar.gz' --output /tmp/pql.tgz
|
||||||
|
tar -xzf /tmp/pql.tgz -C /tmp
|
||||||
|
sudo install -m 0755 /tmp/pql /usr/local/bin/pql
|
||||||
|
pql --version
|
||||||
|
# The pql tests query the repo's vault, but .pql/pql.db is gitignored
|
||||||
|
# (the post-checkout hook rebuilds it from the committed changelog).
|
||||||
|
# A fresh CI checkout has the changelog but no db — materialize it,
|
||||||
|
# and sync decision records from the governance/ DQR markdown tree
|
||||||
|
# (tickets come from the changelog; decisions from `decisions sync`).
|
||||||
|
pql plan import
|
||||||
|
pql decisions sync
|
||||||
|
# Point the real release app's crash logs at an uploadable workspace dir
|
||||||
|
# (T-436): if the bundle wedges on boot, the watchdog heartbeat/sample +
|
||||||
|
# FileLogSink land here and get uploaded below. CLIDE_LOG=debug so the
|
||||||
|
# file sink captures info/debug, not just the release-default warn.
|
||||||
|
- name: bundle smoke (logs → artifact)
|
||||||
|
env:
|
||||||
|
CLIDE_LOG: debug
|
||||||
|
CLIDE_LOG_DIR: ${{ github.workspace }}/clide-logs
|
||||||
|
run: make smoke-bundle
|
||||||
|
- name: Upload crash logs
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: bundle-crash-logs
|
||||||
|
path: ${{ github.workspace }}/clide-logs
|
||||||
|
if-no-files-found: ignore
|
||||||
|
|
||||||
|
web-wasm:
|
||||||
|
name: web build (wasm compile gate)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with: { channel: stable, cache: true }
|
||||||
|
- run: flutter pub get
|
||||||
|
# Compile gate for the dart:ffi web fence (D-100 / T-438, resolving Q-50):
|
||||||
|
# every native binding lives behind a `dart.library.ffi` conditional import
|
||||||
|
# with a web stub. If a new one lands without its stub, this fails — the
|
||||||
|
# fence can't silently rot. Build-only for now; the full web-WASM Playwright
|
||||||
|
# e2e (`make test-e2e`: setup-node + playwright install in tools/ui +
|
||||||
|
# serve) is the follow-on once the harness is wired back up.
|
||||||
|
- run: flutter build web --wasm
|
||||||
|
|
||||||
|
docs:
|
||||||
|
name: dart doc (lib API)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with: { channel: stable, cache: true }
|
||||||
|
- run: flutter pub get
|
||||||
|
- name: dart doc --validate-links (fail on warning)
|
||||||
|
run: |
|
||||||
|
set -o pipefail
|
||||||
|
dart doc --validate-links 2>&1 | tee dartdoc.log
|
||||||
|
if grep -q "^ warning:" dartdoc.log; then
|
||||||
|
echo "::error::dartdoc emitted warnings — see log above"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dart-api-docs
|
||||||
|
path: doc/api/
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
name: windows-soak
|
||||||
|
|
||||||
|
# ConPTY orphan-leak soak on a GitHub-hosted Windows runner — the cheap
|
||||||
|
# alternative to a dedicated Windows VM. The freeze hypothesis (T-424) is that
|
||||||
|
# each WindowsPty.start() leaks its conhost/OpenConsole host because the child
|
||||||
|
# is not in a kill-on-close Job Object; across many runs those hosts pile up
|
||||||
|
# until the box starves. tools/windows-verify/soak-conpty.ps1 reproduces that
|
||||||
|
# WITHOUT crashing: it runs the ConPTY suite many times IN ONE job and counts
|
||||||
|
# the hosts that survive each dart.exe exit. A throwaway runner is fine — we
|
||||||
|
# watch the accumulation (the leading indicator), not the reboot. The repeated
|
||||||
|
# runs happen inside this single job, so the leak can build up here even though
|
||||||
|
# the runner is discarded afterwards (cf. the note in windows.yml, which only
|
||||||
|
# runs the suite once).
|
||||||
|
#
|
||||||
|
# Diagnostic, never a gate: it always exits 0 and just publishes the verdict +
|
||||||
|
# CSV. Runs on demand (workflow_dispatch) and when the soak kit itself changes.
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
iterations:
|
||||||
|
description: How many times to run the ConPTY suite (clean-path soak)
|
||||||
|
default: "25"
|
||||||
|
kill_iterations:
|
||||||
|
description: Spawn+force-kill cycles (abrupt-death orphan probe)
|
||||||
|
default: "15"
|
||||||
|
ptys_per_iter:
|
||||||
|
description: WindowsPty sessions spawned per kill cycle
|
||||||
|
default: "2"
|
||||||
|
push:
|
||||||
|
branches: [windows-support]
|
||||||
|
paths:
|
||||||
|
- tools/windows-verify/**
|
||||||
|
- .github/workflows/windows-soak.yml
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
conpty-soak:
|
||||||
|
runs-on: windows-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
- run: flutter --version
|
||||||
|
- run: flutter pub get
|
||||||
|
- name: ConPTY orphan-leak soak
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$iters = "${{ github.event.inputs.iterations }}"
|
||||||
|
if (-not $iters) { $iters = "25" }
|
||||||
|
tools/windows-verify/soak-conpty.ps1 -Iterations ([int]$iters) -OutDir "$env:GITHUB_WORKSPACE/soak-out"
|
||||||
|
- name: Publish verdict to job summary
|
||||||
|
if: always()
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$s = Get-ChildItem "$env:GITHUB_WORKSPACE/soak-out/*.summary.txt" -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
|
if ($s) { Get-Content $s.FullName | Add-Content $env:GITHUB_STEP_SUMMARY }
|
||||||
|
- name: Upload soak CSV + summary
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: conpty-soak
|
||||||
|
path: soak-out
|
||||||
|
if-no-files-found: warn
|
||||||
|
|
||||||
|
conpty-kill-probe:
|
||||||
|
# Abrupt-death half: force-kill the parent dart.exe mid-life (no close(),
|
||||||
|
# no Job Object) and count the ConPTY hosts that survive. This is the path
|
||||||
|
# the freeze hypothesis (T-424) actually implicates — the clean-path soak
|
||||||
|
# above never exercises it. Diagnostic only; always succeeds.
|
||||||
|
runs-on: windows-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
- run: flutter --version
|
||||||
|
- run: flutter pub get
|
||||||
|
- name: ConPTY abrupt-death orphan probe
|
||||||
|
shell: pwsh
|
||||||
|
# CLIDE_LOG_DIR makes the probe emit FFI breadcrumbs (T-436): when a
|
||||||
|
# parent is force-killed mid-life, its reader/waiter isolates' last
|
||||||
|
# crumb ("ReadFile enter" / "WaitForSingleObject enter") is fsynced to
|
||||||
|
# clide-pty.crumbs.log and uploaded below — naming what the wedged
|
||||||
|
# isolate was doing at the instant of death.
|
||||||
|
env:
|
||||||
|
CLIDE_LOG_DIR: ${{ github.workspace }}/kill-crumbs
|
||||||
|
run: |
|
||||||
|
$iters = "${{ github.event.inputs.kill_iterations }}"
|
||||||
|
if (-not $iters) { $iters = "15" }
|
||||||
|
$ptys = "${{ github.event.inputs.ptys_per_iter }}"
|
||||||
|
if (-not $ptys) { $ptys = "2" }
|
||||||
|
tools/windows-verify/soak-conpty-kill.ps1 -Iterations ([int]$iters) -PtysPerIter ([int]$ptys) -OutDir "$env:GITHUB_WORKSPACE/kill-out"
|
||||||
|
- name: Publish verdict to job summary
|
||||||
|
if: always()
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$s = Get-ChildItem "$env:GITHUB_WORKSPACE/kill-out/*.summary.txt" -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
|
if ($s) { Get-Content $s.FullName | Add-Content $env:GITHUB_STEP_SUMMARY }
|
||||||
|
- name: Upload kill-probe CSV + summary
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: conpty-kill-probe
|
||||||
|
path: kill-out
|
||||||
|
if-no-files-found: warn
|
||||||
|
- name: Upload FFI breadcrumbs (last act of each killed reader/waiter)
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: conpty-kill-crumbs
|
||||||
|
path: ${{ github.workspace }}/kill-crumbs
|
||||||
|
if-no-files-found: ignore
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
name: windows
|
||||||
|
|
||||||
|
# Windows CI on GitHub-hosted runners — the only hosted Windows available, and
|
||||||
|
# GitHub is clide's primary remote (the Gitea secondary is self-hosted Linux and
|
||||||
|
# keeps running the Linux suite). This is the first real execution of the ConPTY
|
||||||
|
# backend (lib/src/pty/windows_pty.dart), so expect genuine failures until the
|
||||||
|
# Windows fixes land (T-424). Keep this OUT of required status checks until it's
|
||||||
|
# reliably green — it reports + uploads artifacts without blocking merges. Each
|
||||||
|
# run is a fresh, discarded runner, so the accumulation freeze (which needs
|
||||||
|
# repeated runs on one machine) can't build up here.
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, windows-support]
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
windows-pty:
|
||||||
|
runs-on: windows-latest
|
||||||
|
timeout-minutes: 25
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
- run: flutter --version
|
||||||
|
- run: flutter pub get
|
||||||
|
# No `flutter analyze` here: it's platform-agnostic — the Linux job already
|
||||||
|
# analyzes windows_pty.dart and everything else statically, and the
|
||||||
|
# `flutter build windows` release job catches Windows-specific compile
|
||||||
|
# errors. Skipping it also avoids needing `make gen-build-info`, since the
|
||||||
|
# pty tests import the pty libraries directly, not the build_info-bearing
|
||||||
|
# barrel (lib/clide.dart). This job's unique value is running real ConPTY.
|
||||||
|
- name: ConPTY + Windows-arg unit tests
|
||||||
|
# windows_pty_test.dart drives real ConPTY (it self-skips off-Windows);
|
||||||
|
# the args/size suites are the pure-logic coverage. --timeout 60s so a
|
||||||
|
# wedged reader fails fast instead of hanging the runner.
|
||||||
|
run: dart test --concurrency=1 --timeout 60s test/pty/windows_pty_test.dart test/pty/windows_pty_args_test.dart test/pty/pty_size_test.dart
|
||||||
|
- name: Upload test output / logs
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows-test-output
|
||||||
|
# T-425 (crash-survivable FileLogSink) writes under %LOCALAPPDATA%\clide\logs;
|
||||||
|
# add that path here once it lands so a freeze leaves a downloadable log.
|
||||||
|
path: test/.test-output
|
||||||
|
if-no-files-found: ignore
|
||||||
@@ -58,6 +58,8 @@ tools/ui/.serve.pid
|
|||||||
/native/linux-x64/clide
|
/native/linux-x64/clide
|
||||||
/native/macos-arm64/clide
|
/native/macos-arm64/clide
|
||||||
/native/macos-x64/clide
|
/native/macos-x64/clide
|
||||||
|
/native/windows-x64/clide.exe
|
||||||
|
/native/windows-x64/clide.obj
|
||||||
|
|
||||||
# -- Test, coverage, profile output ------------------------------------
|
# -- Test, coverage, profile output ------------------------------------
|
||||||
*.test
|
*.test
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: "cc0734ac716fbb8b90f3f9db8020958b1553afa7"
|
revision: "c9a6c484230f8b5e408ec57be1ef71dee1e77020"
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
@@ -13,11 +13,11 @@ project_type: app
|
|||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7
|
create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
|
||||||
base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7
|
base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
|
||||||
- platform: web
|
- platform: windows
|
||||||
create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7
|
create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
|
||||||
base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7
|
base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
|||||||
@@ -26,3 +26,12 @@ INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updat
|
|||||||
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DMF20SYFDT6WX2RFBQXKW', '06FB3DNQZKV20F7YG5PJH8V8SM', '2026-06-10 13:27:09', '2026-06-10 13:27:09', NULL, '1d5185ae9c9676bd70d0e02e3a5e79a1', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DMF20SYFDT6WX2RFBQXKW', '06FB3DNQZKV20F7YG5PJH8V8SM', '2026-06-10 13:27:09', '2026-06-10 13:27:09', NULL, '1d5185ae9c9676bd70d0e02e3a5e79a1', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||||
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DQEMTDHF8SV27AKAB8JHW', '06FB3DNQZKV20F7YG5PJH8V8SM', '2026-06-10 13:27:10', '2026-06-10 13:27:10', NULL, '90dca94aa700c143290b2b1afaca09ed', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DQEMTDHF8SV27AKAB8JHW', '06FB3DNQZKV20F7YG5PJH8V8SM', '2026-06-10 13:27:10', '2026-06-10 13:27:10', NULL, '90dca94aa700c143290b2b1afaca09ed', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||||
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DMF20SYFDT6WX2RFBQXKW', '06FB3DP48FS33CQGRDF7EB9GT0', '2026-06-10 13:27:10', '2026-06-10 13:27:10', NULL, '9b9081edc77f0e9a4b689bbc191771db', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DMF20SYFDT6WX2RFBQXKW', '06FB3DP48FS33CQGRDF7EB9GT0', '2026-06-10 13:27:10', '2026-06-10 13:27:10', NULL, '9b9081edc77f0e9a4b689bbc191771db', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||||
|
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DJZDDZ00BSA04B660RS7M', '06FB3DQEMTDHF8SV27AKAB8JHW', '2026-06-10 13:27:05', '2026-06-12 01:11:12', NULL, '17f1c884268a172f803f407a2ad47c8c', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||||
|
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DQEMTDHF8SV27AKAB8JHW', '06FB3DN94MBCTYJW17ZCYVSXE0', '2026-06-10 13:27:09', '2026-06-12 01:11:17', NULL, 'a67368ff15089dce57838840632fe07e', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||||
|
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DQEMTDHF8SV27AKAB8JHW', '06FB3DNQZKV20F7YG5PJH8V8SM', '2026-06-10 13:27:10', '2026-06-12 01:11:22', NULL, '0ec8ff6c455136e45fbb1d06a2a690f1', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||||
|
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DMF20SYFDT6WX2RFBQXKW', '06FB3DP48FS33CQGRDF7EB9GT0', '2026-06-10 13:27:10', '2026-06-12 01:11:26', NULL, '3c985828c591c88d492eb261c6d26d33', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||||
|
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DQEMTDHF8SV27AKAB8JHW', '06FB3DMF20SYFDT6WX2RFBQXKW', '2026-06-12 01:11:31', '2026-06-12 01:11:31', NULL, '81fd318a43138a3bef82e31f928ff0b2', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||||
|
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKMV7Y13PAYKZC0WB4FQXKC', '06FBKMXSVCE98K1H76N00TYCQR', '2026-06-12 03:16:35', '2026-06-12 03:16:35', NULL, '28d9785a1f9696b6b579a1dbb9fdb889', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||||
|
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKMV7Y13PAYKZC0WB4FQXKC', '06FBKN09R21H3AWR2Q2ZTSGNSW', '2026-06-12 03:16:40', '2026-06-12 03:16:40', NULL, '7e00348c10432c65b03f9dce36520e48', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||||
|
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKMXSVCE98K1H76N00TYCQR', '06FBKN2MP35NPPK1BRDYY2M428', '2026-06-12 03:16:44', '2026-06-12 03:16:44', NULL, 'a6b1c20279ac30f692a30c802cf8f3e5', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||||
|
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKMV7Y13PAYKZC0WB4FQXKC', '06FBKN4QVFVE51MY2N0CWCVXHM', '2026-06-12 03:16:49', '2026-06-12 03:16:49', NULL, '9063328f18ba32c0737997ac9af0911a', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -178,3 +178,127 @@ INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_
|
|||||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB5HMYDXP62RKH3HP55T6AYG', 'T-351', '2026-06-10 18:23:41', '2026-06-10 18:23:41', NULL, '9d2da44c16c5aa38c0a36e4b00ef5f15', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB5HMYDXP62RKH3HP55T6AYG', 'T-351', '2026-06-10 18:23:41', '2026-06-10 18:23:41', NULL, '9d2da44c16c5aa38c0a36e4b00ef5f15', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB5M14B76B31654D959XM5AC', 'T-352', '2026-06-10 18:34:05', '2026-06-10 18:34:05', NULL, '3fe3e1d5fb7c0fbd084b45116575ad98', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB5M14B76B31654D959XM5AC', 'T-352', '2026-06-10 18:34:05', '2026-06-10 18:34:05', NULL, '3fe3e1d5fb7c0fbd084b45116575ad98', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBAWHM1SQ1686ZJ8JQCFQ1ZW', 'T-353', '2026-06-11 06:50:21', '2026-06-11 06:50:21', NULL, '53374633101d04f94981baaf4f2e0315', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBAWHM1SQ1686ZJ8JQCFQ1ZW', 'T-353', '2026-06-11 06:50:21', '2026-06-11 06:50:21', NULL, '53374633101d04f94981baaf4f2e0315', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBDSJYQFDNKP4KA1JAEDSS8W', 'T-354', '2026-06-11 13:36:51', '2026-06-11 13:36:51', NULL, '32bb5d359401599022a8771031d8a08a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBDSKGAHYHH2NPZK8B6EV4D4', 'T-355', '2026-06-11 13:36:55', '2026-06-11 13:36:55', NULL, '2eb6809e958613a924b35e080ab17609', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBDSM0PRGYR61R0NWYAT9VDC', 'T-356', '2026-06-11 13:37:00', '2026-06-11 13:37:00', NULL, 'bdb597080218a3e8783f6c5cf74c529a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBDSPECQ0FPKB9SYTD7KZSBM', 'T-357', '2026-06-11 13:37:19', '2026-06-11 13:37:19', NULL, '96cf88e036dc3a45487cdbffff26cdde', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBDSQ2GBSP0ZH4RHZG2PMR0R', 'T-358', '2026-06-11 13:37:25', '2026-06-11 13:37:25', NULL, '2a656aaec54bf50c34c7e28347b1fb29', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHBGHNEQTAEPGNJKN42C1E8', 'T-359', '2026-06-11 21:54:36', '2026-06-11 21:54:36', NULL, '1930fad7b18c79cf97d913d8372b77bd', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHBJ5T7HAQ9CA8XQMX43A2C', 'T-360', '2026-06-11 21:54:49', '2026-06-11 21:54:49', NULL, '1ffef3c00cdca28566ab67a11ec17e53', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHBKK2TZQK683J8FS0ZH5A4', 'T-361', '2026-06-11 21:55:01', '2026-06-11 21:55:01', NULL, '5f8bdede98496496bf031a40a09dee16', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHBN5F0F8SDF15P21DNKT1W', 'T-362', '2026-06-11 21:55:13', '2026-06-11 21:55:13', NULL, '610e74eb7b4ff9db3949ed01a0268380', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHBPQE4J4YBJX92812ZK6DR', 'T-363', '2026-06-11 21:55:26', '2026-06-11 21:55:26', NULL, '5daf7bcd9ccdc37f8aed531ef12d395f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHBR4636GSRJBWFJDAZ6ZA0', 'T-364', '2026-06-11 21:55:38', '2026-06-11 21:55:38', NULL, '025de46ba9e8d005fd8b1f74687cd8b6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHBSG6356MZJ2DCCCSBMBGM', 'T-365', '2026-06-11 21:55:49', '2026-06-11 21:55:49', NULL, 'b366b41e4737a2761a01284fd7dd44e0', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHBV0465906BY3QFAY9F1YM', 'T-366', '2026-06-11 21:56:01', '2026-06-11 21:56:01', NULL, '8a75dc7dea83a0e643d1912bda46dd57', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHBWE2W1226T58CX37E50HC', 'T-367', '2026-06-11 21:56:13', '2026-06-11 21:56:13', NULL, '578b51eafd19044dc0e2720f6e55d633', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHBYTJ4E7ZBY6DWWNT1S16M', 'T-368', '2026-06-11 21:56:33', '2026-06-11 21:56:33', NULL, '0df409c83fe28af2d49a156118ed6ece', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHC0HYRZ86CWW0DDQJ5CAQM', 'T-369', '2026-06-11 21:56:47', '2026-06-11 21:56:47', NULL, '7f47ddc3e84f9fea915200992a5a0baf', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHC2NM7AYKENZ0ZD49HAX1W', 'T-370', '2026-06-11 21:57:04', '2026-06-11 21:57:04', NULL, '1908b7c0903c389ad5b743fb89ca32e0', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHC46SEHH8NQY481VMGK66R', 'T-371', '2026-06-11 21:57:17', '2026-06-11 21:57:17', NULL, '2095f10159561a5e81b2a9b990c79fa2', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHC5ZE4EZEGXK8YY8J86CM0', 'T-372', '2026-06-11 21:57:31', '2026-06-11 21:57:31', NULL, '9d22f370e726af56d47d6c8acd93bc87', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHC7KDFW07S8WTCC3MD71J0', 'T-373', '2026-06-11 21:57:44', '2026-06-11 21:57:44', NULL, '231c399183a83e569c0645e1d149625f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHC90B72A270CAKA7AP1ZX8', 'T-374', '2026-06-11 21:57:56', '2026-06-11 21:57:56', NULL, '9832ac0b3e0bebd85f3271a5ea96d4ed', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCAFKK334YNJXZJQG4J6AW', 'T-375', '2026-06-11 21:58:08', '2026-06-11 21:58:08', NULL, 'de3569d098b5c4cf6a3d49ee3d33d1c9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCC6AR37VTF4SY8DR99JHC', 'T-376', '2026-06-11 21:58:22', '2026-06-11 21:58:22', NULL, 'fe23c94d8971d21963a2b2e2739e05a3', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCEC25337J2AXXQNST56Y4', 'T-377', '2026-06-11 21:58:40', '2026-06-11 21:58:40', NULL, '1585df7f3826ddbcef8a030ffd1e0890', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCG84F4SPFW111CC4K26A8', 'T-378', '2026-06-11 21:58:55', '2026-06-11 21:58:55', NULL, 'c6b0d9d124401f1d2bdc403567cbfdf8', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCJEYHC91PMVNVWVHBR2RG', 'T-379', '2026-06-11 21:59:13', '2026-06-11 21:59:13', NULL, '0d9aed402c9840ef6fb75edfcfcbc3f4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCM1RBAF72SCZBKRTXJSYC', 'T-380', '2026-06-11 21:59:26', '2026-06-11 21:59:26', NULL, '1a0d767f30c9bc0e9c96f39d468ca67b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCP03EJ9CDBGZGRPD19N8W', 'T-381', '2026-06-11 21:59:42', '2026-06-11 21:59:42', NULL, 'cfac236e079164f9588d936f5101c71c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCQHZQ0NKY1VRWWPSNZT84', 'T-382', '2026-06-11 21:59:55', '2026-06-11 21:59:55', NULL, 'b54be44ec4cfbbc649324471a5e2141f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCST6CQ449VJGAP6C5KZ5W', 'T-383', '2026-06-11 22:00:14', '2026-06-11 22:00:14', NULL, '0e18de4e94e4fcf36fc40de763522cbd', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCVPGCKEGDC54KKQ120SRM', 'T-384', '2026-06-11 22:00:29', '2026-06-11 22:00:29', NULL, '57d7b6a6c05791acadd1cebe611c8991', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCXFF5V1RT6QJETS2K4C0G', 'T-385', '2026-06-11 22:00:44', '2026-06-11 22:00:44', NULL, 'ebda2dd9c7dfe92bab2260dc34212ac4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHD098CV2N73823KX4Z99P4', 'T-386', '2026-06-11 22:01:07', '2026-06-11 22:01:07', NULL, 'de90501f2d4be80231651d25d04f4649', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHD2FWTDFYA00W4QXTE41M0', 'T-387', '2026-06-11 22:01:25', '2026-06-11 22:01:25', NULL, '8a247364a872c223b37682c6496d3920', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHD4QYHYRTK0SGRZCBSHSQ0', 'T-388', '2026-06-11 22:01:43', '2026-06-11 22:01:43', NULL, 'cc238ee850d4d0ed90d05b9a42c8d506', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHD6FQMXMQQQ877KMNCK6QC', 'T-389', '2026-06-11 22:01:57', '2026-06-11 22:01:57', NULL, 'bb0657e304f4ad1445d756ef7170c62d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHD8F2NBEFZNPKSJ9W253J0', 'T-390', '2026-06-11 22:02:14', '2026-06-11 22:02:14', NULL, '35453f0d8c7e0fb89c243481f870de03', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHDAK04ZA0PBT69ZWNBXPSR', 'T-391', '2026-06-11 22:02:31', '2026-06-11 22:02:31', NULL, '0647245b4da95532bb3abd6f20cd87de', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHDC7B2MFQZVR1K9E30FXDR', 'T-392', '2026-06-11 22:02:44', '2026-06-11 22:02:44', NULL, 'a1612ff75df3d134b8e05c716d24ebfe', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHDE5A3965GNRFS5WPZW878', 'T-393', '2026-06-11 22:03:00', '2026-06-11 22:03:00', NULL, 'f8f29fb8b31cc8afdd8af989c3f711f1', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHDGPXQN31NNRPJ00PFRAG4', 'T-394', '2026-06-11 22:03:21', '2026-06-11 22:03:21', NULL, 'f9cee5dc96cedfabc3eaaf7352e732c8', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHDH8TE9MJBXZ4GQ5YT10JM', 'T-395', '2026-06-11 22:03:26', '2026-06-11 22:03:26', NULL, '382a5742e32da0f38c1e143715a0b656', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBJAXQHCKHS8ZSDZNM9NH7QM', 'T-396', '2026-06-12 00:11:50', '2026-06-12 00:11:50', NULL, '20577481e56f82bb0df9e56a266303c9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBJM6XXQZ3EMGRC13XRYVBEM', 'T-397', '2026-06-12 00:52:25', '2026-06-12 00:52:25', NULL, 'bf89e25b384be60faa65e9eb1ec2fab9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKMV7Y13PAYKZC0WB4FQXKC', 'T-398', '2026-06-12 03:15:00', '2026-06-12 03:15:00', NULL, 'd0ed46ee279c148f1d76683e86e0d4aa', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKMXSVCE98K1H76N00TYCQR', 'T-399', '2026-06-12 03:15:21', '2026-06-12 03:15:21', NULL, 'eacf3522aeb2bccbaf006c9703b9794d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKN09R21H3AWR2Q2ZTSGNSW', 'T-400', '2026-06-12 03:15:41', '2026-06-12 03:15:41', NULL, 'a7971f428145d04ae82d4cd89f3eeb9d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKN2MP35NPPK1BRDYY2M428', 'T-401', '2026-06-12 03:16:00', '2026-06-12 03:16:00', NULL, '29980034db5fe381473b156ece7d8a1a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKN4QVFVE51MY2N0CWCVXHM', 'T-402', '2026-06-12 03:16:18', '2026-06-12 03:16:18', NULL, 'c65717bf20ea6886ef7a86f5cb4b2929', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKP67X1Y1FEE9T5R0E5DA9C', 'T-403', '2026-06-12 03:20:52', '2026-06-12 03:20:52', NULL, '372a686b214820b5d093b11b9f3d57a5', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKP8KFAF526ZNXBQS98DPPG', 'T-404', '2026-06-12 03:21:11', '2026-06-12 03:21:11', NULL, 'c479a9dea6687b553bc638a9849cc5de', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKPAZR4XEV8YW3PVR2XJBFC', 'T-405', '2026-06-12 03:21:31', '2026-06-12 03:21:31', NULL, 'e4e1695f838b8fbf02aae49a6f2df4fe', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKPD85PFBPJJTQ0WS3PWWXR', 'T-406', '2026-06-12 03:21:49', '2026-06-12 03:21:49', NULL, '689352238d2050a3769b2a9613f0a793', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKPFTC7H5NY0XHBTEGF8XQ4', 'T-407', '2026-06-12 03:22:10', '2026-06-12 03:22:10', NULL, '129d2b3c31022d53025a3e28169a060e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBN3VTK2MYQQ173MSJN6E1DM', 'T-408', '2026-06-12 06:40:25', '2026-06-12 06:40:25', NULL, '0353aaab57a40900b00883fb12e135f7', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBN3VYR84023Z5XFEX9DS0S0', 'T-409', '2026-06-12 06:40:26', '2026-06-12 06:40:26', NULL, '69c280319335a8d0ef646998f4531e96', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBP3EZC7AJANXZVF3D91QYWM', 'T-410', '2026-06-12 08:58:29', '2026-06-12 08:58:29', NULL, '4f726d1a66d38d14018a62c8d24ffe62', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBP3GM6V0RZBY2PXE9ZQFR88', 'T-411', '2026-06-12 08:58:42', '2026-06-12 08:58:42', NULL, 'bdd3f8b13caf25677ac661ec29599488', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBP3J7TXMG0F9E2WQDENPVJG', 'T-412', '2026-06-12 08:58:55', '2026-06-12 08:58:55', NULL, 'e56d50bc6fc04f6d646f22c104e63183', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBP3KRWM65MD3DS251NN9YX0', 'T-413', '2026-06-12 08:59:08', '2026-06-12 08:59:08', NULL, 'b5247ea05c106500682be978a47e61ee', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBP3P8YERJ5R7ENSD675BX00', 'T-414', '2026-06-12 08:59:28', '2026-06-12 08:59:28', NULL, '7d973f16c99441dbba0f8df89a665b32', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBP3P91QQQDT5J50F52FPCKM', 'T-415', '2026-06-12 08:59:28', '2026-06-12 08:59:28', NULL, 'a2dea9268d44b9e8a1fd746bbb947c92', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPQ8QNGJFFK7G24CBWQAR2C', 'T-416', '2026-06-12 10:25:00', '2026-06-12 10:25:00', NULL, 'f8c2a125e661607d5dd0c73cd2c3f2ab', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBQ4BYD4STCKCY8JNKF23Q4W', 'T-417', '2026-06-12 11:22:15', '2026-06-12 11:22:15', NULL, '15aa9b25417162126cbcde174d3537da', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBQ595H08JFTRFSR90GSZQ0G', 'T-418', '2026-06-12 11:26:14', '2026-06-12 11:26:14', NULL, '000e07ae64b08273a2d2d9f8a77d193f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBTTMGKSYMTF8M1KQWTG774W', 'T-419', '2026-06-12 19:58:58', '2026-06-12 19:58:58', NULL, 'd2b01a2c3d1ce24cc863ac6d9d814d3d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FC2XY1T85A65YY9SG25VVEY4', 'T-420', '2026-06-13 14:51:51', '2026-06-13 14:51:51', NULL, '16ea4c9353a56798b894ab3d85fb7b56', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDG3T4A7CYPTG535KVAVH6C', 'T-421', '2026-06-14 15:29:23', '2026-06-14 15:29:23', NULL, 'a28eed4b57104034c5216344a326195c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDKX4CVHWVGDAJC6X09602M', 'T-422', '2026-06-14 15:45:57', '2026-06-14 15:45:57', NULL, '5701c634f5737a2ba1612deab8df7049', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDM61KAA3GV3CVTE8PAZ8N0', 'T-423', '2026-06-14 15:47:10', '2026-06-14 15:47:10', NULL, 'ce7dfb8b9bd088b4c2e8ddfacc8d2124', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCENXW1VF7VXQX64982X171R', 'T-424', '2026-06-14 18:14:36', '2026-06-14 18:14:36', NULL, 'ecbed75dcd34c39bcbd86e60d6f4a2c2', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCENXXZFBZ0HVD1VCW4ZASCC', 'T-425', '2026-06-14 18:14:36', '2026-06-14 18:14:36', NULL, 'b09fc55465f7de02cf98f69c99e0e6e3', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCEP60AS6AF654SWA189A5ZR', 'T-426', '2026-06-14 18:15:42', '2026-06-14 18:15:42', NULL, '91f50c6e38332047f8619db428d4b376', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCEP642C8ZZ1T20RXQQ3143M', 'T-427', '2026-06-14 18:15:43', '2026-06-14 18:15:43', NULL, '55a937def177025ef6b61a222d88b142', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCEP67ZHMBFW0GRH9JKDMQ7R', 'T-428', '2026-06-14 18:15:44', '2026-06-14 18:15:44', NULL, '185e7d3ce8529fbe1f4543f4c635f200', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCEP6BDBHGMK9VCRV6JQ00TW', 'T-429', '2026-06-14 18:15:45', '2026-06-14 18:15:45', NULL, 'ef268614a716384a7597b3e304a6c176', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCEP6EVN9S35T02MHA2AS7YW', 'T-430', '2026-06-14 18:15:46', '2026-06-14 18:15:46', NULL, 'd6333df4da5b7ab8958149a9f0d17974', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCGJ30V24BJB001GZCR5QKTC', 'T-431', '2026-06-14 22:37:27', '2026-06-14 22:37:27', NULL, 'b886820e87f5abd329126bf5e9f1a3da', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCM9ER04JVFW8CN3JW1AWYA8', 'T-432', '2026-06-15 07:18:58', '2026-06-15 07:18:58', NULL, '71f3e4c95f66abc5e7e5820548201e41', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCM9F446MZFXVHH65Q6CKTPM', 'T-433', '2026-06-15 07:19:01', '2026-06-15 07:19:01', NULL, 'b9a361f2c29b286bc808dbea57a05a7e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCM9FHC8VX50759X35VNER1R', 'T-434', '2026-06-15 07:19:04', '2026-06-15 07:19:04', NULL, '403c4c8aa5659bb379cb8add9dff800b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCM9FYDEXCM15FXTER032K84', 'T-435', '2026-06-15 07:19:08', '2026-06-15 07:19:08', NULL, '7c2ed604aecea99b742b341166cf2257', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCM9GAQ2G0KCVMZS67SK3324', 'T-436', '2026-06-15 07:19:11', '2026-06-15 07:19:11', NULL, '3114ab57de9b03aa1e745af01001eee1', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCNYXXH5AAHZR7WV0550J3RC', 'T-437', '2026-06-15 11:12:36', '2026-06-15 11:12:36', NULL, '74dd08c8c42f556959746ee1a47561e6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCQ8HB61N3TWVJ8YSMHH2TJ4', 'T-438', '2026-06-15 14:14:23', '2026-06-15 14:14:23', NULL, 'fa072ae8dd819784440bb44b5fe689d8', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCQHWQ40AY6SNVRJ86YWA0J8', 'T-439', '2026-06-15 14:55:15', '2026-06-15 14:55:15', NULL, '2427484ebb324d731cb8e099a2ad04ab', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCQZ47MAN835B215GSSMRV8W', 'T-440', '2026-06-15 15:53:05', '2026-06-15 15:53:05', NULL, 'f219bb1a70eb1f49e30975ce8c526051', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCZDVPBWGM5NHJ9BNQBVKCD0', 'T-441', '2026-06-16 09:16:07', '2026-06-16 09:16:07', NULL, 'fbabc4fc344b34476b38a186c2f44a16', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCZGG38FF8T9ADF945Z0XPMG', 'T-442', '2026-06-16 09:27:39', '2026-06-16 09:27:39', NULL, 'c85d8fd95cd73aec1e83b477612e1131', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCRERT7X5WMZSGKQCA6T0VB4', 'T-440', '2026-06-15 17:01:25', '2026-06-15 17:01:25', NULL, 'b8e152bf87ec8d81d1bddeb023f37883', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCQZ47MAN835B215GSSMRV8W', 'T-443', '2026-06-15 15:53:05', '2026-06-16 10:17:57', NULL, 'a4d4390596d24d7dc28d80861e36d66b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD07ECS3GK2V7Z2WYYPJHJYC', 'T-444', '2026-06-16 11:07:54', '2026-06-16 11:07:54', NULL, 'ec7a0d8d01c984d931855a6e5c4e86ec', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD09C4A0HPZSP8HP44F7A894', 'T-445', '2026-06-16 11:16:20', '2026-06-16 11:16:20', NULL, '418d561d50d37c3dd9b6e3abe5fc1b1f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD09DH88J1659FDDYJ83JH1M', 'T-446', '2026-06-16 11:16:31', '2026-06-16 11:16:31', NULL, '7c0bc42be1a170b5bb2dd8bd7b027d69', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD09SHDMGT66C4D1DRZQZ7RR', 'T-447', '2026-06-16 11:18:10', '2026-06-16 11:18:10', NULL, '203dfb0348a94392f2d087b79aee655f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD09TVT3E3ZSV09QRNTSQ8J4', 'T-448', '2026-06-16 11:18:20', '2026-06-16 11:18:20', NULL, '765a91fb5b51889861a41f7eea308ea6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD09WB2XSRP0AZ204FDP681M', 'T-449', '2026-06-16 11:18:32', '2026-06-16 11:18:32', NULL, '1dbd17ff345551d3f091a226bb0f7164', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD09XWEXRJ4JS8MFPQGJEY5M', 'T-450', '2026-06-16 11:18:45', '2026-06-16 11:18:45', NULL, '56f97ccefa83681c387ee9cc4ca6a477', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD0A0ZTS9JZCSQ4W8ZVEH2VR', 'T-451', '2026-06-16 11:19:11', '2026-06-16 11:19:11', NULL, 'c329f6f1a5d205b7bdf5ab682812a6d1', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD0A4MY8DF6NB0KNV96RQADR', 'T-452', '2026-06-16 11:19:41', '2026-06-16 11:19:41', NULL, '71a7729a8cf7ebc4e915a9e2736717bd', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD0A6K9B60ADTFBZA6NBVJFM', 'T-453', '2026-06-16 11:19:57', '2026-06-16 11:19:57', NULL, 'd408d563785166ee1b40e7494c7f3bbc', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD0ABC7QNEC3XCTV73YPSGR4', 'T-454', '2026-06-16 11:20:36', '2026-06-16 11:20:36', NULL, '27e2ae5b0897eb0f589f4617061101a9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD15S1A0WRTY9WWFG0R4AAZ4', 'T-455', '2026-06-16 13:20:25', '2026-06-16 13:20:25', NULL, '370e913c62eab739cf39bda08089e033', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD15TY946689VZK3MNABSMDW', 'T-456', '2026-06-16 13:20:41', '2026-06-16 13:20:41', NULL, '6041202d66dd93ee3d22206ece3a7db7', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD15W7SBVAD3QR5NVE67PKWR', 'T-457', '2026-06-16 13:20:52', '2026-06-16 13:20:52', NULL, 'c9bef415fb7d9d03444fde53ecac3bb1', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD1HK7YKJTEK1WV4VHK0RT8R', 'T-458', '2026-06-16 14:12:04.212', '2026-06-16 14:12:04.212', NULL, '070f1def635ee013df639cb9fe81e357', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD1JBRABJHX804CPMZJDC444', 'T-459', '2026-06-16 14:15:25.011', '2026-06-16 14:15:25.011', NULL, '44d5db70ef13ead00c4dcc437c387cec', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD91A7VEW3VCY7QX1END2Z3G', 'T-460', '2026-06-17 07:39:25.019', '2026-06-17 07:39:25.019', NULL, 'e7e749d35dc413a560d3347be5f4a9ee', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD91RWDWZPHFPJ72FHHHWS08', 'T-461', '2026-06-17 07:41:24.975', '2026-06-17 07:41:24.975', NULL, '4e3fba7aaad762b7b7cd854459cf664a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDA0TPYSWEM10RP0Q76XAP58', 'T-462', '2026-06-17 09:57:06.422', '2026-06-17 09:57:06.422', NULL, '4be5f7eb00ca81eb07015af05ed144d3', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDA0VPB99NGS4J6Z5B2RJM2M', 'T-463', '2026-06-17 09:57:14.458', '2026-06-17 09:57:14.458', NULL, 'd5d02453175411c13d21361b084494bd', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDA0WCPG458GF5FXTXM9VY98', 'T-464', '2026-06-17 09:57:20.180', '2026-06-17 09:57:20.180', NULL, 'c787ef61befc6698286ebdaf613e2a41', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDA0X5WFME83GS0DKYG3WVJ8', 'T-465', '2026-06-17 09:57:26.627', '2026-06-17 09:57:26.627', NULL, '116b1a377950dfcef4e5b6db90141faa', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDA0XMA9BQV4RHJ55BFN4JR4', 'T-466', '2026-06-17 09:57:30.322', '2026-06-17 09:57:30.322', NULL, 'd25bc26eb29445fc35cf53f3d54049e5', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDA0YCY2WGSG8W46RHQ5Z9MW', 'T-467', '2026-06-17 09:57:36.624', '2026-06-17 09:57:36.624', NULL, 'a47c675ca67b86ca3c8d4c1a8acc5b80', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDA0YYCJ0NK8C4HMDYKJHRDG', 'T-468', '2026-06-17 09:57:41.092', '2026-06-17 09:57:41.092', NULL, 'b988d9578b1ca079c040f07265a008b4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDA10DRQE3SED10JAW2CZDCR', 'T-469', '2026-06-17 09:57:53.221', '2026-06-17 09:57:53.221', NULL, '2a72e3e647afc1b5b235ab1a97d298f8', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDB1VFADV4QK1YG29T658T8C', 'T-470', '2026-06-17 12:21:23.411', '2026-06-17 12:21:23.411', NULL, '0f95e5a1faa7a7e902c765f449cceb15', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDJ17C3GZWNE98NRVXP189C', 'T-471', '2026-06-17 18:11:42.049', '2026-06-17 18:11:42.049', NULL, '10a9a957d647a63ce69dc4b6b564f6aa', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDTCGG5Z0KNQKF89W1VZSQ8', 'T-472', '2026-06-17 18:48:11.649', '2026-06-17 18:48:11.649', NULL, '097120cff851cb2dac9f54937f4c7b17', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDX5GVH3FTCVDEC1QAFACY4', 'T-473', '2026-06-17 19:00:20.828', '2026-06-17 19:00:20.828', NULL, '647782edc3fbb4e7285e4f82a1fb84be', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDDX5KEAJ8KRWVV7ZSG6H7A0', 'T-474', '2026-06-17 19:00:21.490', '2026-06-17 19:00:21.490', NULL, '79576f18553ed885d3a2b745becdf2f5', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDEMY3DQWDNY535PMMSB0CSW', 'T-475', '2026-06-17 20:44:11.501', '2026-06-17 20:44:11.501', NULL, 'bb2b56ea9a76d967123238f588c70aba', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDXN3ZBRS6JK7Q8G6JVSPXFC', 'T-476', '2026-06-19 07:42:08.735', '2026-06-19 07:42:08.735', NULL, 'f75a0a8c00b0ec40c110c1169e42fe2c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
INSERT INTO ticket_labels (ticket_record_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDG3T4A7CYPTG535KVAVH6C', 'multi-window', '2026-06-14 15:29:27', '2026-06-14 15:29:27', NULL, 'ce03f8eb534bc45e2c1c12e9b30a2c30', 2) ON CONFLICT(ticket_record_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
|
||||||
|
INSERT INTO ticket_labels (ticket_record_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDG3T4A7CYPTG535KVAVH6C', 'ipc', '2026-06-14 15:29:28', '2026-06-14 15:29:28', NULL, 'e6789b66db5c1de85daf6fc8d3474449', 2) ON CONFLICT(ticket_record_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
|
||||||
|
INSERT INTO ticket_labels (ticket_record_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCNYXXH5AAHZR7WV0550J3RC', 'regression', '2026-06-15 11:12:41', '2026-06-15 11:12:41', NULL, 'd1aee45df369a9afed1ecc7bc6043255', 2) ON CONFLICT(ticket_record_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
|
||||||
|
INSERT INTO ticket_labels (ticket_record_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCNYXXH5AAHZR7WV0550J3RC', 'claude-cli', '2026-06-15 11:12:41', '2026-06-15 11:12:41', NULL, '2d486f878edbd7a898ece18900e0d9d6', 2) ON CONFLICT(ticket_record_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
|
||||||
|
INSERT INTO ticket_labels (ticket_record_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCQ8HB61N3TWVJ8YSMHH2TJ4', 'web', '2026-06-15 14:22:06', '2026-06-15 14:22:06', NULL, '6867c9766259a7f09e81ff515973914e', 2) ON CONFLICT(ticket_record_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
|
||||||
|
INSERT INTO ticket_labels (ticket_record_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCQHWQ40AY6SNVRJ86YWA0J8', 'path', '2026-06-15 15:24:11', '2026-06-15 15:24:11', NULL, 'e73b3b85732a2c21be23353a0f7f1493', 2) ON CONFLICT(ticket_record_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
|
||||||
|
INSERT INTO ticket_labels (ticket_record_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCQHWQ40AY6SNVRJ86YWA0J8', 'web', '2026-06-15 15:24:11', '2026-06-15 15:24:11', NULL, '8c93337891e74d67d2465ba2c3b901d5', 2) ON CONFLICT(ticket_record_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
|
||||||
|
INSERT INTO ticket_labels (ticket_record_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCQZ47MAN835B215GSSMRV8W', 'web', '2026-06-15 15:53:35', '2026-06-15 15:53:35', NULL, '4cbf5007b12087b88b8acb36f3616bf8', 2) ON CONFLICT(ticket_record_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
|
||||||
File diff suppressed because it is too large
Load Diff
+413
@@ -16,6 +16,419 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
## [2.8.0] — 2026-06-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Language selector + Dutch (nl-NL).** Settings → Appearance → Language
|
||||||
|
switches the UI language live (persisted as `app.locale`); a full Dutch
|
||||||
|
translation ships. English (en-US) stays the default. (T-462)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **App is fully localizable (i18n everywhere).** Every user-facing label —
|
||||||
|
panels, dialogs, command palette, menus, and settings — now resolves through
|
||||||
|
the i18n catalog instead of a hardcoded string; catalogs are bundled per
|
||||||
|
locale under `assets/i18n/<locale>/`. A new language is a drop-in folder;
|
||||||
|
en_US behaviour is unchanged. (T-462)
|
||||||
|
|
||||||
|
## [2.7.1] — 2026-06-18
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Josefin Sans is the default UI font again.** Reverts the Inter default from
|
||||||
|
2.7.0; Inter stays bundled and selectable in Settings → Appearance. JetBrains
|
||||||
|
Mono remains the default monospace face (Fira Mono selectable). (T-460)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Markdown prose honours the UI-font setting.** Claude's conversation prose
|
||||||
|
and inline links render through the markdown engine, which pinned the bundled
|
||||||
|
UI face and ignored the Appearance UI-font pick; it now follows the setting
|
||||||
|
live, like the rest of the app. (T-475)
|
||||||
|
|
||||||
|
## [2.7.0] — 2026-06-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Settings panel shell.** A new `settings.open` command (⌘`,`, plus a File
|
||||||
|
menu and command-palette entry) opens a centered Settings modal over the
|
||||||
|
dimmed app — the foundation of the schema-driven settings UI. It frames the
|
||||||
|
category rail and scrolling carded panel that later work fills in; closes on
|
||||||
|
✕, Esc, or a barrier tap. (T-445)
|
||||||
|
|
||||||
|
- **Schema-driven settings engine.** Subsystems register a `SettingsCategory`
|
||||||
|
(via `SettingsCategoryContribution` → the kernel `SettingsRegistry`); the
|
||||||
|
panel renders it as carded sections of toggle/select/text/number/file rows,
|
||||||
|
each bound to a `SettingsStore` key with help and reset-to-default.
|
||||||
|
Registering a category surfaces a new tab. (T-448)
|
||||||
|
|
||||||
|
- **Settings category rail.** The modal's left rail lists the registered
|
||||||
|
categories (icon + title, data-driven from the registry) with an accent
|
||||||
|
left-stripe + surfaceHi selection; picking one swaps the panel. (T-447)
|
||||||
|
|
||||||
|
- **Per-field scope tags.** Each field shows where its value lives — folder =
|
||||||
|
Project (`.clide`), globe = Always (`~/.clide`), circle-dashed =
|
||||||
|
Default/unset — with a tooltip and a menu to move the value between scopes or
|
||||||
|
reset it. Backed by scope-explicit `SettingsStore` access. (T-449)
|
||||||
|
|
||||||
|
- **Cross-category settings search.** A search box atop the rail filters fields
|
||||||
|
across every category; the panel shows the matches grouped under category
|
||||||
|
subheaders (editable inline), and each rail row shows its match count with
|
||||||
|
zero-match categories dimmed. (T-450)
|
||||||
|
|
||||||
|
- **Settings → Activity category.** The first real settings tab: the
|
||||||
|
conversation fold level (none / tools / thinking / everything) as a schema
|
||||||
|
field; picking a level applies live to the activity stream. (T-453)
|
||||||
|
|
||||||
|
- **Settings → Keymap category.** A preset select (Default / Vim / VS Code /
|
||||||
|
JetBrains); picking one switches the active keymap live via the preset
|
||||||
|
command. (T-451)
|
||||||
|
|
||||||
|
- **Settings → Appearance category.** A theme picker in the panel — base-theme
|
||||||
|
chips + a high-contrast toggle, applied live. Adds the engine's custom-control
|
||||||
|
escape hatch (`SettingsControlContribution` / `SettingsControlRegistry`) for
|
||||||
|
one-off controls the generic field kinds can't express. (T-452)
|
||||||
|
|
||||||
|
- **Settings → Extensions tab.** A "watch this space" notice — installing and
|
||||||
|
toggling extensions arrives with third-party (Lua) support; built-ins stay
|
||||||
|
always-on for now. (T-456)
|
||||||
|
|
||||||
|
- **Settings → Claude category.** New-session defaults — model, effort, and
|
||||||
|
permission mode — seed fresh sessions (effort via `--effort` at spawn;
|
||||||
|
model and permission applied right after start). (T-457)
|
||||||
|
|
||||||
|
- **Inter is the default UI font + a UI-font picker.** Bundled Inter (variable)
|
||||||
|
as the default interface typeface — Josefin Sans stays selectable — and
|
||||||
|
Settings → Appearance gains a UI-font picker that applies live. (T-460)
|
||||||
|
|
||||||
|
- **Monospace font picker.** Settings → Appearance adds a monospace font select
|
||||||
|
(JetBrains Mono / Fira Mono) that applies live to the terminal, diffs, code,
|
||||||
|
and IDs. Bundles Fira Mono. (T-471)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Theme picker relabelled "Theme…".** The ⌘K theme picker's command title
|
||||||
|
changed from "Settings…" to "Theme…" so it no longer collides with the new
|
||||||
|
Settings panel in the palette; behaviour is unchanged. (T-445)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Monospace setting now applies everywhere.** Eleven context-free render
|
||||||
|
helpers (Claude tool bodies + results, inline markdown code/refs, search
|
||||||
|
previews, welcome tips) hard-coded JetBrains Mono and ignored the Monospace
|
||||||
|
font setting; they now honour it live. (T-472)
|
||||||
|
|
||||||
|
## [2.6.0] — 2026-06-16
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Vim `gt` / `gT` tab motions.** Under the Vim preset, `gt`/`gT` cycle the
|
||||||
|
workspace tab strip (the same `workspace.tab.*` commands as `ctrl+pagedown`/
|
||||||
|
`ctrl+pageup`), resolved by the focused editor or pane and sharing the `g`
|
||||||
|
prefix with `gg`. Completes the T-403 cross-pane vim layer. (T-405)
|
||||||
|
|
||||||
|
- **Vim ex command-line (`:`).** Under the Vim preset, `:` opens a transient
|
||||||
|
one-line overlay running a fixed table — `:w` save, `:q` close the active tab
|
||||||
|
(the split self-collapses on the last one), `:wq`/`:x` and `ZZ` save+close,
|
||||||
|
`:e <path>` jump to quick-open seeded with the path, `:<n>` goto-line. Unknown
|
||||||
|
commands flash and stay open; with no active buffer it no-ops. Opens from the
|
||||||
|
editor or a focused pane; Esc dismisses. Adds the `editor.goto-line` CLI/IPC
|
||||||
|
verb. (T-407)
|
||||||
|
|
||||||
|
- **Crash-survivable logging.** clide writes a durable JSON-lines log to a
|
||||||
|
persistent per-platform dir (Windows `%LOCALAPPDATA%`, macOS `~/Library/Logs`,
|
||||||
|
Linux `$XDG_STATE_HOME`), fsyncing warn/error + pty/ffi records immediately so
|
||||||
|
a freeze leaves on-disk evidence. `CLIDE_LOG` (dart-define / env) or the
|
||||||
|
`app.log.level` setting sets verbosity (warn in release, info in debug);
|
||||||
|
`CLIDE_LOG_DIR` redirects where the logs land. (T-432, T-436)
|
||||||
|
- **PTY FFI breadcrumbs.** Each PTY backend drops a breadcrumb before/after
|
||||||
|
every risky syscall (`CreatePseudoConsole`/`CreateProcessW`/`ReadFile`,
|
||||||
|
`posix_spawn`/`read`); the reader/waiter isolates fsync their OWN file handle
|
||||||
|
so a wedged isolate's last crumb survives a freeze that also froze the main
|
||||||
|
isolate — naming the wedge after the fact. Per-syscall crumbs at debug level.
|
||||||
|
(T-434)
|
||||||
|
- **Crash-diagnostic watchdog.** A dedicated isolate fsyncs a heartbeat every
|
||||||
|
~500ms (bounding a freeze to ~500ms) and every ~2s samples this process's
|
||||||
|
thread / handle / child-host / RSS counts to `clide-watchdog.log` — a climbing
|
||||||
|
child or thread count is the leak signature. Survives a frozen main isolate;
|
||||||
|
spawn failure is non-fatal. (T-435)
|
||||||
|
- **Live log-verbosity toggle.** The output dock's Level chip now sets the
|
||||||
|
running logger's level and persists `app.log.level` (not just a view filter),
|
||||||
|
and `clide log level [<level>]` does the same from the CLI — D-6 parity. The
|
||||||
|
choice survives restart. (T-433)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **`/clear` no longer kills the Claude pane.** Clearing the primary pane tore
|
||||||
|
the session down and respawned on the same deterministic `--session-id`
|
||||||
|
*before the old `claude` process had actually exited*, so claude 2.1.177
|
||||||
|
rejected the id as "already in use" and the respawn exited 1. The session
|
||||||
|
teardown now awaits the process's real death (SIGTERM, escalating to SIGKILL)
|
||||||
|
before clearing the transcript and respawning. A dead pane also now shows the
|
||||||
|
CLI's own reason instead of a bare "exited (code 1)". (T-437)
|
||||||
|
- **Web/WASM build compiles again.** `flutter build web --wasm` (and the
|
||||||
|
`test-e2e` / `ui-dev` / `ui-smoke` harness) had been broken since the
|
||||||
|
tree-sitter/PTY `dart:ffi` pivot. Every native binding (PTY, tree-sitter, the
|
||||||
|
Lua host, the Windows watchdog, the ABI/fd-inheritance probes) now sits behind
|
||||||
|
a `dart.library.ffi` conditional import with a graceful web stub, and the FNV
|
||||||
|
hash constants are dart2js-safe. A `flutter build web --wasm` compile gate was
|
||||||
|
added to CI so the fence can't silently rot. Desktop builds are unchanged — the
|
||||||
|
web target degrades (no terminal, no native git, no syntax highlighting), it
|
||||||
|
does not compromise desktop fidelity. (T-438, D-100, Q-50)
|
||||||
|
- **Desktop-launched clide finds your installed tools.** A dock/launcher start
|
||||||
|
inherits a sparse `PATH` (no `~/.local/bin`, brew, nvm, …), so pql/git/claude
|
||||||
|
and terminal tools could go missing. clide now resolves the real login-shell
|
||||||
|
`PATH` once at startup (`$SHELL -l -c`, bounded + graceful fallback) and routes
|
||||||
|
every spawn site — PTY children, git, the toolchain probe, hosted claude —
|
||||||
|
through one shared resolver, replacing three divergent (and partly macOS-only)
|
||||||
|
PATH expanders. (T-439, follows T-347)
|
||||||
|
|
||||||
|
## [2.5.0] — 2026-06-14
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Experimental Windows desktop support.** clide builds and runs on Windows —
|
||||||
|
ConPTY-backed terminals, an AF_UNIX `clide` CLI client, PowerShell as the
|
||||||
|
default shell, and a `make build-windows` target. Preview quality: ConPTY
|
||||||
|
child-process reaping under sustained use is still being hardened. (T-424)
|
||||||
|
- **Vim `ctrl+w` window commands.** Under the vim preset, `ctrl+w` followed by
|
||||||
|
h/l (focus left/right panel), j (toggle dock), w / ctrl+w (cycle panels),
|
||||||
|
shift+w (cycle back), o (focus mode), or q/c (close editor). A new global
|
||||||
|
multi-chord matcher in the shell resolves these from any focus; bare `ctrl+w`
|
||||||
|
still closes the editor after the ambiguity timeout. (T-404)
|
||||||
|
- **Workspace tab cycling with ctrl+pagedown / ctrl+pageup.** New
|
||||||
|
`workspace.tab.next` / `workspace.tab.previous` commands cycle the workspace
|
||||||
|
tab strip with wraparound, bound across every preset. (T-405)
|
||||||
|
- **Vim normal-mode navigation works outside the editor.** Under the vim preset,
|
||||||
|
a focused file tree or conversation now responds to j/k, ctrl+d/ctrl+u, gg/G,
|
||||||
|
and (tree) h/l/o — a selection cursor in the tree, scrolling in the
|
||||||
|
conversation. Each pane runs its own sequence matcher; an `editor.focused`
|
||||||
|
flag keeps these keys as buffer motions while the editor holds focus. (T-406)
|
||||||
|
- **Claude Code Workflow runs surface in the conversation and sidebar.** A
|
||||||
|
`Workflow` tool-use renders a dedicated run card — phase groups, per-agent
|
||||||
|
rows with live spinner/check status, usage, and the script — driven by the
|
||||||
|
harness's out-of-band progress events. The Activity tab adds a WORKFLOWS
|
||||||
|
section showing each run's done/total agent count. (T-416)
|
||||||
|
- **Session controls and live usage in the Claude sidebar Activity tab.** A
|
||||||
|
SESSION strip offers clear/compact/fork/resume buttons (same code path as the
|
||||||
|
typed commands), and a refresh control fetches `/usage` — plan usage renders
|
||||||
|
as a USAGE block (session and weekly percentages). The runtime row now also
|
||||||
|
shows the session's effort level. (T-415)
|
||||||
|
- **The Claude sidebar Config tab is a live control panel.** Model, effort, and
|
||||||
|
permission mode are popover controls showing the running session's values;
|
||||||
|
picking an option drives the session through the same path as the typed slash
|
||||||
|
command. The sidebar tables also got a visual pass — larger type, accent
|
||||||
|
section headers, more breathing room. (T-414)
|
||||||
|
- **The TUI command family opens clide surfaces.** `/permissions` sets the mode
|
||||||
|
directly or opens a picker; `/status`, `/config`, `/mcp`, `/agents`, `/hooks`
|
||||||
|
jump to the matching Claude sidebar tab; `/memory` opens CLAUDE.md in the
|
||||||
|
editor; `/help` shows clide's own command summary. (T-413)
|
||||||
|
- **`/effort` works in the Claude pane.** With a level (`/effort xhigh`) the
|
||||||
|
session restarts in place carrying `--effort` — resume keeps the
|
||||||
|
conversation; bare `/effort` opens a picker with the five levels and the
|
||||||
|
current one marked. The active effort shows in the session status. (T-412)
|
||||||
|
- **TUI-only slash commands get a helpful notice instead of failing.** A typed
|
||||||
|
`/cost` or `/doctor` no longer errors raw from the CLI or leaks to the model
|
||||||
|
as literal text — known TUI-only commands route to a muted notice card with
|
||||||
|
the clide-native way. CLI-local output (like `/usage`) renders as a "clide"
|
||||||
|
card, never fake Claude prose. (T-411)
|
||||||
|
- **`/model` works in the Claude pane.** With a name (`/model sonnet`) it
|
||||||
|
switches the live session's model over the control channel; bare `/model`
|
||||||
|
opens a picker in the interaction zone with the CLI's model list and the
|
||||||
|
current model marked. A rejected name rolls back and raises a toast. (T-408)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- **tmux is no longer a required tool.** clide stopped spawning tmux when Claude
|
||||||
|
session persistence moved to `--resume` (D-77); the toolchain no longer probes
|
||||||
|
for it or warns when it's absent, on any platform.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **`ClaudeConfig` no longer crashes on a project switch that races teardown.**
|
||||||
|
`setProjectDir` / `refresh` / `ensureProbe` now skip `notifyListeners()` if the
|
||||||
|
config was disposed during their async load (the guard `load()` already had).
|
||||||
|
|
||||||
|
## [2.4.1] — 2026-06-12
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **`Shift+;` types a colon again — double-Shift no longer fires on chorded
|
||||||
|
Shift.** The double-tap detector counted any Shift press as a tap, even
|
||||||
|
mid-chord, and never saw keys the focused editor consumed; a tap now
|
||||||
|
requires a clean press-and-release, observed at the raw-keyboard level.
|
||||||
|
(T-409)
|
||||||
|
|
||||||
|
## [2.4.0] — 2026-06-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Live tail inside expanded Bash activity cards.** A Bash card that follows a
|
||||||
|
file (`tail -f …`) now shows a live, scrolling read-only tail of that file
|
||||||
|
below the result — connected only while the card is expanded. Commands with no
|
||||||
|
followable file show a muted "nothing to follow" note. (T-325)
|
||||||
|
|
||||||
|
- **Double-tap-modifier shortcuts (e.g. double-Shift "Search Everywhere").**
|
||||||
|
The keymap can now bind a bare modifier and a double-tap sequence
|
||||||
|
(`shift shift`). All four presets (default, vim, vscode, jetbrains) map
|
||||||
|
double-Shift to the quick-open finder — JetBrains' "Search Everywhere"
|
||||||
|
gesture, aliased to clide's existing fuzzy file finder. (T-341)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Each spawned subagent gets its own collapsing activity card.** A fan-out of
|
||||||
|
N agents no longer merges into one "Activity / N steps" cluster — each spawn is
|
||||||
|
its own card with its prompt and nested run. Non-agent tool calls still group
|
||||||
|
as before. (T-342)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- **Dead-code sweep.** The legacy free-function git API (with its latent
|
||||||
|
pipe deadlock), ToolCheck, the fd-passing-era libc bindings, the GraphView
|
||||||
|
placeholder, the superseded ColumnHat widget, the tmux-era
|
||||||
|
TranscriptPublisher, the committed `ptyc` binary, and the unused
|
||||||
|
`mocktail` dev-dependency (D-25 amended) are gone. (T-385)
|
||||||
|
|
||||||
|
- **Dead welcome-screen tiles.** "Clone from git…" and "Start a Claude
|
||||||
|
session" did nothing on tap and advertised shortcuts that were never
|
||||||
|
registered; the tips card now lists only shortcuts that exist in the
|
||||||
|
default keymap. Each tile returns when its flow ships. (T-383)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Terminal CSI sequences with intermediate bytes no longer mis-dispatch.**
|
||||||
|
The parser dropped intermediates, so e.g. VT420 scroll-left (`CSI 5 SP @`)
|
||||||
|
executed as "insert 5 blank characters"; such sequences are now reported
|
||||||
|
as unknown instead. (T-123)
|
||||||
|
|
||||||
|
- **PTY master fd no longer leaks when a child exits on its own.** Every
|
||||||
|
terminal or Claude pane whose process ended naturally left its pty device
|
||||||
|
open for the life of the app; natural exit now releases the fd. (T-360)
|
||||||
|
|
||||||
|
- **Closing a terminal pane now closes its shell.** Pane disposal looked up
|
||||||
|
the kernel illegally and swallowed the failure, so `pane.close` was never
|
||||||
|
sent — the backend PTY and daemon pane leaked on every closed terminal
|
||||||
|
pane, and Claude panes leaked a settings listener the same way. (T-366)
|
||||||
|
|
||||||
|
- **Scrolling up during a streaming reply no longer fights the auto-scroll.**
|
||||||
|
The conversation followed the tail on every streamed token regardless of
|
||||||
|
scroll position, dragging a reader back to the bottom; it now follows only
|
||||||
|
while already pinned there. (T-368)
|
||||||
|
|
||||||
|
- **The terminal no longer crashes on truncated SGR color sequences.**
|
||||||
|
`ESC[38m` and friends threw a RangeError inside the emulator; incomplete
|
||||||
|
38/48 sequences are now ignored, and colon-form truecolor/256-color
|
||||||
|
sub-parameters (`38:2:r:g:b`, ITU T.416) parse like the semicolon form
|
||||||
|
instead of being mangled. (T-369)
|
||||||
|
|
||||||
|
- **File listing flags symlinks again and the workspace walk no longer
|
||||||
|
follows them.** Symlink detection was dead code, so `files.walk` (and the
|
||||||
|
search engine on top of it) silently descended symlinked directories —
|
||||||
|
including ones pointing outside the workspace. (T-365)
|
||||||
|
|
||||||
|
- **Search-and-replace now honors its include/exclude globs.** The filters
|
||||||
|
were accepted but never applied, so replace could rewrite files outside
|
||||||
|
the scope the user typed; replace now uses the same glob filtering as
|
||||||
|
search. (T-364)
|
||||||
|
|
||||||
|
- **Switching projects releases the previous workspace's services.** The old
|
||||||
|
file watcher, pane PTYs, in-flight searches, and editor buffers were left
|
||||||
|
alive on every project switch, with stale watcher events leaking into the
|
||||||
|
new workspace. (T-367)
|
||||||
|
|
||||||
|
- **Expanded activity cards are readable by screen readers again.** The
|
||||||
|
collapser's summarized button semantics excluded the whole card, so
|
||||||
|
expanding a run announced nothing inside it; the exclusion is now scoped
|
||||||
|
to the header and the inner cards stay in the a11y tree. (T-370)
|
||||||
|
|
||||||
|
- **A crashed Claude process no longer looks like it's still thinking.**
|
||||||
|
stderr is now drained continuously (an undrained pipe could block the
|
||||||
|
child mid-turn) and the exit code is watched: when the process dies the
|
||||||
|
pane stops spinning, clears any unanswerable permission prompt, reports
|
||||||
|
the exit in the status line, and logs the stderr tail. (T-361)
|
||||||
|
|
||||||
|
- **The Claude status bar populates reliably after a session starts.** The
|
||||||
|
session's init event often fired before the pane subscribed and the plain
|
||||||
|
broadcast stream dropped it, leaving the model/mode/context line blank;
|
||||||
|
session state streams now replay their latest value to late subscribers.
|
||||||
|
(T-274, T-386)
|
||||||
|
|
||||||
|
- **New terminal panes open in the project root.** The shell spawned in the
|
||||||
|
app process's working directory — `$HOME` for desktop launches, and the
|
||||||
|
wrong repo after a project switch. (T-381)
|
||||||
|
|
||||||
|
- **Two simultaneous spawns of the same Claude session no longer leak a
|
||||||
|
process.** Concurrent spawn calls for one pane id both passed the registry
|
||||||
|
check and the loser's live process was orphaned; spawns for an id are now
|
||||||
|
coalesced onto one in-flight future. (T-374)
|
||||||
|
|
||||||
|
- **`/clear` in a fork pane clears instead of re-forking.** The fork source
|
||||||
|
took precedence on every respawn, so clearing a fork tab silently branched
|
||||||
|
the original conversation again; the source now seeds only the first
|
||||||
|
bind. (T-375)
|
||||||
|
|
||||||
|
- **Pipelined IPC requests are now truly serial and framing-safe.** The
|
||||||
|
server's read handler could interleave concurrent requests (against
|
||||||
|
D-72's contract), drop or double frames split across reads, and corrupt
|
||||||
|
multi-byte characters split across chunks. (T-372)
|
||||||
|
|
||||||
|
- **Settings survive nested structures, crashes, and corruption.** Maps
|
||||||
|
inside lists (the keymap overlay shape) were corrupted on save; writes
|
||||||
|
are now atomic (temp file + rename), and a file that fails to parse is
|
||||||
|
preserved as `.broken` with a logged warning instead of being silently
|
||||||
|
reset. (T-376)
|
||||||
|
|
||||||
|
- **Markdown hard breaks break lines and images leave a visible trace.**
|
||||||
|
Both rendered as empty text — words on either side of a hard break glued
|
||||||
|
together and images vanished; breaks now emit a newline and images render
|
||||||
|
an italic `[image: alt]` placeholder. (T-379)
|
||||||
|
|
||||||
|
- **Extension notifications actually appear on screen.** Messages pushed
|
||||||
|
through the kernel Notifications service (e.g. the CLI-install dogfood
|
||||||
|
warnings) accumulated in a list no surface rendered; they now raise
|
||||||
|
toasts with matching severity. (T-382)
|
||||||
|
|
||||||
|
- **Failed `clide claude.*` commands now exit non-zero.** Sixteen handlers
|
||||||
|
reported success with an error message buried in the payload, so scripts
|
||||||
|
could not detect failures like an unknown permission mode; they now
|
||||||
|
return proper error envelopes per the D-6 contract. (T-391)
|
||||||
|
|
||||||
|
- **Terminal output no longer garbles multi-byte characters split across
|
||||||
|
reads.** PTY output and live-tail bytes were decoded per chunk, turning a
|
||||||
|
rune split across reads into replacement-character noise; the terminal
|
||||||
|
now ingests bytes through a persistent decoder. (T-373)
|
||||||
|
|
||||||
|
- **Extension lifecycle is transactional.** A throw mid-activation now
|
||||||
|
unwinds every contribution it had mounted (a retry no longer
|
||||||
|
double-applies), deactivating an extension is refused while active
|
||||||
|
extensions depend on it, and duplicate contribution/command ids are
|
||||||
|
rejected instead of silently clobbering. (T-377)
|
||||||
|
|
||||||
|
- **Accepting ExitPlanMode now leaves plan mode in the conversation panel.**
|
||||||
|
Approving Claude's plan (the ExitPlanMode tool) transitioned the underlying
|
||||||
|
session out of plan mode, but clide's tracked permission mode didn't follow,
|
||||||
|
so the mode indicator and composer stayed stuck on "plan". The approval now
|
||||||
|
syncs the tracked mode to `default`. (T-337)
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- **The MCP HTTP server now requires a per-start auth token.** The localhost
|
||||||
|
SSE port served the entire clide command surface unauthenticated,
|
||||||
|
bypassing the unix socket's 0600 gate; requests must now present the
|
||||||
|
token published in the 0600 `/ide` lock file. (T-362)
|
||||||
|
|
||||||
|
- **`editor.open` / `editor.save` are now workspace-confined.** Both verbs
|
||||||
|
accepted absolute paths and `..` traversal verbatim — an unconfined read
|
||||||
|
and write primitive over IPC. They now pass the same path-safety guard as
|
||||||
|
`files.read`, including a symlink re-check at save time. (T-363)
|
||||||
|
|
||||||
## [2.3.3] — 2026-06-11
|
## [2.3.3] — 2026-06-11
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ An IDE for Claude Code CLI. Single Flutter package at the repo root.
|
|||||||
- **`lib/`** — all Dart code. Subsystem handlers (`lib/src/daemon/`, `lib/src/pty/`, `lib/src/ipc/`, `lib/src/git/`, `lib/src/pql/`), kernel services (`lib/kernel/`), UI widgets (`lib/widgets/`), built-in extensions (`lib/builtin/`), and the extension framework (`lib/extension/`). The Flutter app hosts the IPC server in-process (D-56). PTY spawning uses Dart FFI `posix_openpt()` + `posix_spawn()` directly.
|
- **`lib/`** — all Dart code. Subsystem handlers (`lib/src/daemon/`, `lib/src/pty/`, `lib/src/ipc/`, `lib/src/git/`, `lib/src/pql/`), kernel services (`lib/kernel/`), UI widgets (`lib/widgets/`), built-in extensions (`lib/builtin/`), and the extension framework (`lib/extension/`). The Flutter app hosts the IPC server in-process (D-56). PTY spawning uses Dart FFI `posix_openpt()` + `posix_spawn()` directly.
|
||||||
- **[`pql`](https://github.com/postmeridiem/pql)** — external supporter tool. Clide wraps it for every query surface; never re-implements it.
|
- **[`pql`](https://github.com/postmeridiem/pql)** — external supporter tool. Clide wraps it for every query surface; never re-implements it.
|
||||||
|
|
||||||
tmux owns Claude session persistence (D-41) — the app re-attaches on restart via `tmux new-session -A`. Native rendering — markdown, canvas, graph — is Dart/Flutter (`CustomPaint` + widgets), not third-party packages.
|
Claude session persistence is `--resume <session-id>` against Claude Code's transcript files (D-77, superseding the original tmux-backed D-41) — the app re-attaches on restart, no tmux required. Native rendering — markdown, canvas, graph — is Dart/Flutter (`CustomPaint` + widgets), not third-party packages.
|
||||||
|
|
||||||
Design doc: [`docs/initial-plan.md`](docs/initial-plan.md). Decisions: [`governance/`](governance/) (`D-NNN` confirmed, `Q-NNN` open, `R-NNN` rejected — see [`governance/README.md`](governance/README.md)). Python Textual predecessor under [`legacy/`](legacy/).
|
Design doc: [`docs/initial-plan.md`](docs/initial-plan.md). Decisions: [`governance/`](governance/) (`D-NNN` confirmed, `Q-NNN` open, `R-NNN` rejected — see [`governance/README.md`](governance/README.md)). Python Textual predecessor under [`legacy/`](legacy/).
|
||||||
|
|
||||||
@@ -89,6 +89,8 @@ Shell hygiene (keeps commands inside the permission allowlist, so they don't get
|
|||||||
|
|
||||||
Commit and push directly to `main` for routine work — this is a solo-dev repo and does not use a branch-first / feature-branch flow. Do **not** create a working branch just to land a change. (This overrides the generic "branch before committing on the default branch" assistant default.) The usual safety rules still hold: never `--no-verify`, never force-push `main`, and let the pre-push gate run.
|
Commit and push directly to `main` for routine work — this is a solo-dev repo and does not use a branch-first / feature-branch flow. Do **not** create a working branch just to land a change. (This overrides the generic "branch before committing on the default branch" assistant default.) The usual safety rules still hold: never `--no-verify`, never force-push `main`, and let the pre-push gate run.
|
||||||
|
|
||||||
|
**Never `git add -A` or `git add .` — stage explicit paths every time (`git add <file> …`), no exceptions.** This worktree can host concurrent Claude sessions: a blanket add vacuums another session's in-progress files — and your own unrelated edits — into your commit, mislabeling work and entangling history (this has happened). If `git status` shows files you didn't touch this turn, they are not yours to stage. **Always create commits through the [`git-commit` skill](.claude/skills/git-commit/SKILL.md)** — it encodes the message format (Conventional Commits, per [D-37](governance/decisions/process.md#d-37)), the explicit-staging rule, changelog discipline, and the safety reminders. Don't hand-roll a commit that skips it.
|
||||||
|
|
||||||
The pre-commit hook auto-exports and stages `.pql/changelog/` (the pql ticket DB) on every commit — don't hand-stage it. A ticket change only persists if the turn makes at least one commit; with no commit the hook never fires and a later branch switch can drop it.
|
The pre-commit hook auto-exports and stages `.pql/changelog/` (the pql ticket DB) on every commit — don't hand-stage it. A ticket change only persists if the turn makes at least one commit; with no commit the hook never fires and a later branch switch can drop it.
|
||||||
|
|
||||||
## Changelog discipline
|
## Changelog discipline
|
||||||
|
|||||||
+8
-4
@@ -181,14 +181,18 @@ tickets before the diff lands. Trivial typo fixes don't need one.
|
|||||||
See [D-37](governance/decisions/process.md#d-37) and the bundled
|
See [D-37](governance/decisions/process.md#d-37) and the bundled
|
||||||
[`git-commit` skill](.claude/skills/git-commit/SKILL.md). In short:
|
[`git-commit` skill](.claude/skills/git-commit/SKILL.md). In short:
|
||||||
|
|
||||||
- Imperative subject ≤ 70 chars, no Conventional Commits prefix
|
- [Conventional Commits 1.0](https://www.conventionalcommits.org/en/v1.0.0/):
|
||||||
(this isn't a Conventional Commits repo — the archived Python
|
`type(scope): imperative subject`, ≤ 72 chars including the prefix.
|
||||||
predecessor under [`legacy/`](legacy/) is, but the rebuild isn't).
|
Types are `feat`, `fix`, `docs`, `style`, `refactor`, `perf`,
|
||||||
|
`test`, `build`, `chore`; scope is the subsystem (`settings`,
|
||||||
|
`vim`, `pty`…); keep a trailing `(T-NNN)` ticket ref where one
|
||||||
|
applies — e.g. `feat(settings): category rail + navigation (T-447)`.
|
||||||
- One logical change per commit. If the subject needs "and", split it.
|
- One logical change per commit. If the subject needs "and", split it.
|
||||||
- Every user-visible commit adds an entry to `CHANGELOG.md` under
|
- Every user-visible commit adds an entry to `CHANGELOG.md` under
|
||||||
`[Unreleased]` in the right subsection (Added, Changed, Deprecated,
|
`[Unreleased]` in the right subsection (Added, Changed, Deprecated,
|
||||||
Removed, Fixed, Security). Keep entries to one or two short
|
Removed, Fixed, Security). Keep entries to one or two short
|
||||||
sentences — the 60-word cap is enforced by `ci/changelog_gate.sh`.
|
sentences — the 60-word cap is enforced by the pre-push gate
|
||||||
|
(`make changelog-gate`).
|
||||||
- Co-author trailer:
|
- Co-author trailer:
|
||||||
`Co-Authored-By: Claude <noreply@anthropic.com>` when Claude wrote
|
`Co-Authored-By: Claude <noreply@anthropic.com>` when Claude wrote
|
||||||
any of the diff.
|
any of the diff.
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ changelog-gate: ## Changelog concision gate — fails on `## [Unreleased]` bulle
|
|||||||
ci/changelog_gate.sh
|
ci/changelog_gate.sh
|
||||||
|
|
||||||
.PHONY: smoke-bundle
|
.PHONY: smoke-bundle
|
||||||
smoke-bundle: ## Build Linux release bundle and run it under xvfb for 5s.
|
smoke-bundle: gen-build-info ## Build Linux release bundle and run it under xvfb for 5s.
|
||||||
ci/smoke_bundle.sh
|
ci/smoke_bundle.sh
|
||||||
|
|
||||||
# -- web UI harness ------------------------------------------------------
|
# -- web UI harness ------------------------------------------------------
|
||||||
@@ -182,6 +182,10 @@ build-linux: gen-build-info ## flutter build linux (desktop bundle).
|
|||||||
build-macos: gen-build-info ## flutter build macos (desktop bundle).
|
build-macos: gen-build-info ## flutter build macos (desktop bundle).
|
||||||
flutter build macos
|
flutter build macos
|
||||||
|
|
||||||
|
.PHONY: build-windows
|
||||||
|
build-windows: gen-build-info ## flutter build windows (desktop bundle).
|
||||||
|
flutter build windows
|
||||||
|
|
||||||
# -- install / uninstall -----------------------------------------------------
|
# -- install / uninstall -----------------------------------------------------
|
||||||
|
|
||||||
# Install prefix. Bundle lands at $(INSTALL_PREFIX)/clide/ with a
|
# Install prefix. Bundle lands at $(INSTALL_PREFIX)/clide/ with a
|
||||||
@@ -196,6 +200,9 @@ ifeq ($(FLUTTER_OS),linux)
|
|||||||
else ifeq ($(FLUTTER_OS),macos)
|
else ifeq ($(FLUTTER_OS),macos)
|
||||||
BUNDLE_DIR := build/macos/Build/Products/Release/clide.app
|
BUNDLE_DIR := build/macos/Build/Products/Release/clide.app
|
||||||
CLI_BUNDLE_DEST := $(BUNDLE_DIR)/Contents/MacOS/clide-cli
|
CLI_BUNDLE_DEST := $(BUNDLE_DIR)/Contents/MacOS/clide-cli
|
||||||
|
else ifeq ($(FLUTTER_OS),windows)
|
||||||
|
BUNDLE_DIR := build/windows/x64/runner/Release
|
||||||
|
CLI_BUNDLE_DEST := $(BUNDLE_DIR)/clide-cli.exe
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ICON_SIZES := 16 32 48 128 192 256 512
|
ICON_SIZES := 16 32 48 128 192 256 512
|
||||||
@@ -291,7 +298,11 @@ dugite-clean: ## Remove the dugite-native directory.
|
|||||||
# target picks up whatever `cc` is on PATH.
|
# target picks up whatever `cc` is on PATH.
|
||||||
|
|
||||||
CLIDE_CLI_SRC := native/clide-cli/clide.c
|
CLIDE_CLI_SRC := native/clide-cli/clide.c
|
||||||
CLIDE_CLI_BIN := native/$(if $(filter Darwin,$(shell uname -s)),macos,linux)-$(shell uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/')/clide
|
ifeq ($(FLUTTER_OS),windows)
|
||||||
|
CLIDE_CLI_BIN := native/windows-x64/clide.exe
|
||||||
|
else
|
||||||
|
CLIDE_CLI_BIN := native/$(if $(filter Darwin,$(shell uname -s)),macos,linux)-$(shell uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/')/clide
|
||||||
|
endif
|
||||||
CC ?= cc
|
CC ?= cc
|
||||||
|
|
||||||
.PHONY: clide-cli
|
.PHONY: clide-cli
|
||||||
@@ -299,7 +310,11 @@ clide-cli: $(CLIDE_CLI_BIN) ## Compile the C `clide` shell client.
|
|||||||
|
|
||||||
$(CLIDE_CLI_BIN): $(CLIDE_CLI_SRC)
|
$(CLIDE_CLI_BIN): $(CLIDE_CLI_SRC)
|
||||||
@mkdir -p $(dir $(CLIDE_CLI_BIN))
|
@mkdir -p $(dir $(CLIDE_CLI_BIN))
|
||||||
|
ifeq ($(FLUTTER_OS),windows)
|
||||||
|
ci/build_cli_windows.sh
|
||||||
|
else
|
||||||
$(CC) -std=c99 -O2 -Wall -Wextra -o $(CLIDE_CLI_BIN) $(CLIDE_CLI_SRC)
|
$(CC) -std=c99 -O2 -Wall -Wextra -o $(CLIDE_CLI_BIN) $(CLIDE_CLI_SRC)
|
||||||
|
endif
|
||||||
@echo "==> built $(CLIDE_CLI_BIN)"
|
@echo "==> built $(CLIDE_CLI_BIN)"
|
||||||
|
|
||||||
.PHONY: clide-cli-clean
|
.PHONY: clide-cli-clean
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This document governs what clide is allowed to do at runtime, what it's allowed to depend on, and how contributors — human and agent — introduce code into the project. It is binding on all contributors. When in doubt, stop and ask.
|
This document governs what clide is allowed to do at runtime, what it's allowed to depend on, and how contributors — human and agent — introduce code into the project. It is binding on all contributors. When in doubt, stop and ask.
|
||||||
|
|
||||||
Rationale for specific architectural choices referenced here and in code comments (the D-### markers) lives in `decisions/`. This document sets the rules; `decisions/` records why the rules produced the code they did in a given case. If the two ever disagree, the rule in this document wins until the document itself is changed.
|
Rationale for specific architectural choices referenced here and in code comments (the D-### markers) lives in `governance/decisions/`. This document sets the rules; `governance/decisions/` records why the rules produced the code they did in a given case. If the two ever disagree, the rule in this document wins until the document itself is changed.
|
||||||
|
|
||||||
## Why this document exists
|
## Why this document exists
|
||||||
|
|
||||||
@@ -124,9 +124,9 @@ When removing a dependency:
|
|||||||
|
|
||||||
1. **Grep the entire repository** for references to the package, its exports, and any type names it contributed. `rg '<package>|<PackageType>|<prefix_>'` across the repo. Zero hits outside git history is the goal. A single lingering import will break the build; a single lingering FFI stub or type alias will compile fine and fail at runtime.
|
1. **Grep the entire repository** for references to the package, its exports, and any type names it contributed. `rg '<package>|<PackageType>|<prefix_>'` across the repo. Zero hits outside git history is the goal. A single lingering import will break the build; a single lingering FFI stub or type alias will compile fine and fail at runtime.
|
||||||
2. **Regenerate the lockfile** as part of the same PR. A `pubspec.yaml` with the dep removed but a `pubspec.lock` that still pins it is a partial removal, and CI or a fresh clone will happily continue installing the package.
|
2. **Regenerate the lockfile** as part of the same PR. A `pubspec.yaml` with the dep removed but a `pubspec.lock` that still pins it is a partial removal, and CI or a fresh clone will happily continue installing the package.
|
||||||
3. **Update `app/assets/licenses.yaml`** to drop the removed package and any transitive deps it brought in that aren't pulled by anything else. If the license manifest is auto-generated on release, verify the generation script sees the change; if it's maintained by hand, edit it in the same PR.
|
3. **Update `assets/licenses.yaml`** to drop the removed package and any transitive deps it brought in that aren't pulled by anything else. If the license manifest is auto-generated on release, verify the generation script sees the change; if it's maintained by hand, edit it in the same PR.
|
||||||
4. **Remove any vendored artifacts** tied to the dep — binaries, prebuilt assets, generated bindings — and delete their `BUILD.md` records. An orphaned vendored binary is worse than a removed one because it looks legitimate.
|
4. **Remove any vendored artifacts** tied to the dep — binaries, prebuilt assets, generated bindings — and delete their `BUILD.md` records. An orphaned vendored binary is worse than a removed one because it looks legitimate.
|
||||||
5. **Check for architectural assumptions** that the dep was carrying. If the removed package was the thing that justified a specific data flow, build step, or platform strategy, either the replacement picks up those responsibilities or the architecture has actually changed and the relevant design decision (see `decisions/`) needs updating.
|
5. **Check for architectural assumptions** that the dep was carrying. If the removed package was the thing that justified a specific data flow, build step, or platform strategy, either the replacement picks up those responsibilities or the architecture has actually changed and the relevant design decision (see `governance/decisions/`) needs updating.
|
||||||
|
|
||||||
A dependency is not removed until all five are true. "I deleted the line from pubspec.yaml" is the start of the removal, not the end.
|
A dependency is not removed until all five are true. "I deleted the line from pubspec.yaml" is the start of the removal, not the end.
|
||||||
|
|
||||||
@@ -188,18 +188,18 @@ When in doubt about a license, the dependency does not land until the question i
|
|||||||
|
|
||||||
### Attribution requirements
|
### Attribution requirements
|
||||||
|
|
||||||
- The license manifest at `app/assets/licenses.yaml` lists every dependency with its license, copyright notice, and upstream URL.
|
- The license manifest at `assets/licenses.yaml` lists every dependency with its license, copyright notice, and upstream URL.
|
||||||
- Transitive dependencies are listed, not just direct ones. If `wasm_run` pulls in `wasmtime` which pulls in `cranelift`, all three appear.
|
- Transitive dependencies are listed, not just direct ones. If `wasm_run` pulls in `wasmtime` which pulls in `cranelift`, all three appear.
|
||||||
- Apache-2.0 dependencies get their `NOTICE` file content preserved verbatim, not summarized.
|
- Apache-2.0 dependencies get their `NOTICE` file content preserved verbatim, not summarized.
|
||||||
- Apache-2.0-with-LLVM-exception (e.g., Cranelift, parts of LLVM) requires the LLVM exception text specifically, not just the Apache-2.0 boilerplate.
|
- Apache-2.0-with-LLVM-exception (e.g., Cranelift, parts of LLVM) requires the LLVM exception text specifically, not just the Apache-2.0 boilerplate.
|
||||||
- Fonts and icon sets get attributed even if the license doesn't strictly require it. It's the right thing to do.
|
- Fonts and icon sets get attributed even if the license doesn't strictly require it. It's the right thing to do.
|
||||||
- `app/assets/licenses.yaml` is regenerated as part of the release build, not maintained by hand. A release that ships a stale manifest is a release defect.
|
- `assets/licenses.yaml` is regenerated as part of the release build, not maintained by hand. A release that ships a stale manifest is a release defect.
|
||||||
|
|
||||||
Adding a dependency means updating the license manifest in the same PR. No exceptions.
|
Adding a dependency means updating the license manifest in the same PR. No exceptions.
|
||||||
|
|
||||||
## Changelog and commit conventions
|
## Changelog and commit conventions
|
||||||
|
|
||||||
clide follows [Keep a Changelog 1.1](https://keepachangelog.com/en/1.1.0/) for `CHANGELOG.md` and [Conventional Commits 1.0](https://www.conventionalcommits.org/en/v1.0.0/) for commit messages. Enforcement is handled by the project's git skill; this section exists so human contributors know the standard before their first PR, and so the connection between these conventions and the rest of the policy is explicit.
|
clide follows [Keep a Changelog 1.1](https://keepachangelog.com/en/1.1.0/) for `CHANGELOG.md` and [Conventional Commits 1.0](https://www.conventionalcommits.org/en/v1.0.0/) for commit messages (see [D-37](governance/decisions/process.md#d-37)). Enforcement is handled by the project's git skill; this section exists so human contributors know the standard before their first PR, and so the connection between these conventions and the rest of the policy is explicit.
|
||||||
|
|
||||||
Security-relevant changes — CVE responses, dependency-driven vulnerability fixes, the removal of a phoning-home transitive dep, anything where the rules in this document were the reason for the change — go under the `Security` heading of the release's changelog entry, regardless of whether the code change itself looks security-shaped. That heading is the trail future-us follows to reconstruct why a dep was bumped or removed. Lumping security fixes under `Fixed` because the diff looks like a normal bug fix loses that signal and is the wrong choice even when it's technically accurate.
|
Security-relevant changes — CVE responses, dependency-driven vulnerability fixes, the removal of a phoning-home transitive dep, anything where the rules in this document were the reason for the change — go under the `Security` heading of the release's changelog entry, regardless of whether the code change itself looks security-shaped. That heading is the trail future-us follows to reconstruct why a dep was bumped or removed. Lumping security fixes under `Fixed` because the diff looks like a normal bug fix loses that signal and is the wrong choice even when it's technically accurate.
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ An IDE for Claude Code CLI. Native rendering, terminal-first interaction, pql-po
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
Single Flutter package at the repo root. The app hosts everything in-process: IPC server, subsystem handlers (pane, files, editor, git, pql), and the extension framework. tmux owns Claude session persistence (D-41).
|
Single Flutter package at the repo root. The app hosts everything in-process: IPC server, subsystem handlers (pane, files, editor, git, pql), and the extension framework. Claude session persistence is `--resume <session-id>` against Claude Code's transcript files (D-77, superseding the tmux-backed D-41).
|
||||||
|
|
||||||
- **`lib/`** — all Dart code. Core subsystems (`lib/src/`), kernel services (`lib/kernel/`), UI widgets (`lib/widgets/`), built-in extensions (`lib/builtin/`), the extension framework (`lib/extension/`).
|
- **`lib/`** — all Dart code. Core subsystems (`lib/src/`), kernel services (`lib/kernel/`), UI widgets (`lib/widgets/`), built-in extensions (`lib/builtin/`), the extension framework (`lib/extension/`).
|
||||||
- **PTY** — `lib/src/pty/` spawns child processes via Dart FFI `posix_openpt()` + `posix_spawn()` directly; no external helper binary.
|
- **PTY** — `lib/src/pty/` spawns child processes via Dart FFI `posix_openpt()` + `posix_spawn()` directly; no external helper binary.
|
||||||
@@ -15,7 +15,7 @@ Claude drives the UI through a `clide` CLI surface (Bash, not MCP). Every CLI su
|
|||||||
|
|
||||||
## Built-in extensions
|
## Built-in extensions
|
||||||
|
|
||||||
canvas, claude, claude_control, decisions, diff, editor, extensions_ui, files, git, graph, grammars_core, ipc_status, keybindings_ui, markdown, pql, problems, settings_ui, terminal, theme_picker, tickets, todos, welcome.
|
canvas, claude, claude_control, cli_install, decisions, deeplink, default_layout, diff, editor, extensions_ui, files, git, grammars_core, graph, ipc_status, keybindings_ui, markdown, menubar, output, pql, problems, search, settings_ui, terminal, theme_picker, tickets, todos, view, vim, welcome.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ make push-check # pre-push gate: decisions + core + fast + a11y + coverage
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Pre-v2.0 (`2.0.0-dev`). Interaction model and panel system landed. The Python Textual v1.2.0 predecessor is archived under [`legacy/`](https://github.com/postmeridiem/clide/tree/main/legacy).
|
Active development; the interaction model, panel system, and settings engine have landed. The Python Textual predecessor is archived under [`legacy/`](https://github.com/postmeridiem/clide/tree/main/legacy).
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright (c) 2012-2013, The Mozilla Corporation and Telefonica S.A.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Claude" },
|
||||||
|
"status.attaching": { "translation": "attaching…" },
|
||||||
|
"status.no-tmux": { "translation": "no-tmux · fresh every launch" },
|
||||||
|
"status.exited": { "translation": "session exited" },
|
||||||
|
"status.primary-exited": { "translation": "session exited — restart clide to retry" },
|
||||||
|
"banner.title": { "translation": "Claude" },
|
||||||
|
"banner.warmingUp": { "translation": "Warming up — your conversation will appear here." },
|
||||||
|
"composer.hint": { "translation": "Message Claude… (Enter to send · Shift+Enter for newline)" },
|
||||||
|
"composer.stop": { "translation": "Stop ⎋" },
|
||||||
|
"composer.stop.hint": { "translation": "Interrupt the running turn (Escape)" },
|
||||||
|
"composer.removeAttachment": { "translation": "Remove {name}" },
|
||||||
|
"pane.starting": { "translation": "starting…" },
|
||||||
|
"pane.modeBadge.semantics": { "translation": "permission mode: {mode}" },
|
||||||
|
"running.semantics": { "translation": "Claude is running" },
|
||||||
|
"conversation.empty": { "translation": "Waiting for Claude…" },
|
||||||
|
"conversation.label.you": { "translation": "you" },
|
||||||
|
"conversation.label.claude": { "translation": "claude" },
|
||||||
|
"conversation.label.clide": { "translation": "clide" },
|
||||||
|
"conversation.label.agent": { "translation": "agent" },
|
||||||
|
"conversation.label.agentPrompt": { "translation": "agent prompt" },
|
||||||
|
"conversation.label.context": { "translation": "context" },
|
||||||
|
"conversation.label.thinking": { "translation": "thinking" },
|
||||||
|
"conversation.label.agentThinking": { "translation": "agent thinking" },
|
||||||
|
"conversation.label.image": { "translation": "image" },
|
||||||
|
"conversation.label.agentRun": { "translation": "agent run" },
|
||||||
|
"conversation.label.workflow": { "translation": "workflow" },
|
||||||
|
"conversation.label.error": { "translation": "error" },
|
||||||
|
"conversation.label.result": { "translation": "result" },
|
||||||
|
"conversation.label.denied": { "translation": "denied" },
|
||||||
|
"conversation.segment.prompt": { "translation": "prompt" },
|
||||||
|
"conversation.segment.result": { "translation": "result" },
|
||||||
|
"conversation.segment.liveTail": { "translation": "live tail" },
|
||||||
|
"conversation.segment.usage": { "translation": "usage" },
|
||||||
|
"conversation.segment.script": { "translation": "script" },
|
||||||
|
"conversation.workflow.launching": { "translation": "Launching…" },
|
||||||
|
"conversation.imagePlaceholder": { "translation": "could not load {path}" },
|
||||||
|
"conversation.bashTail.empty": { "translation": "no independent source to follow" },
|
||||||
|
"conversation.bashTail.label": { "translation": "live tail" },
|
||||||
|
"conversation.cluster.activity": { "translation": "Activity" },
|
||||||
|
"conversation.cluster.edits": { "translation": "Edits" },
|
||||||
|
"prompt.permission.allow": { "translation": "1. Allow" },
|
||||||
|
"prompt.permission.allowRemember": { "translation": "2. Allow & don't ask again" },
|
||||||
|
"prompt.permission.deny": { "translation": "{n}. Deny" },
|
||||||
|
"prompt.permission.denySimplify": { "translation": "{n}. Deny & simplify" },
|
||||||
|
"prompt.permission.denySimplify.tooltip": { "translation": "Deny and ask Claude to retry this action in a simpler format — complex interactions don't work well with the permission system." },
|
||||||
|
"prompt.permission.note.placeholder": { "translation": "add a note (optional) — sent to Claude" },
|
||||||
|
"prompt.permission.label": { "translation": "permission · {name}" },
|
||||||
|
"prompt.question.label": { "translation": "question" },
|
||||||
|
"prompt.review.label": { "translation": "review" },
|
||||||
|
"prompt.review.title": { "translation": "Review your answers" },
|
||||||
|
"prompt.submit": { "translation": "Submit" },
|
||||||
|
"prompt.submitAnswers": { "translation": "Submit answers" },
|
||||||
|
"prompt.back": { "translation": "‹ Back" },
|
||||||
|
"prompt.next": { "translation": "Next ›" },
|
||||||
|
"prompt.reviewNav": { "translation": "Review ›" },
|
||||||
|
"prompt.nav.review": { "translation": "Review" },
|
||||||
|
"prompt.option.other": { "translation": "Other…" },
|
||||||
|
"prompt.other.placeholder": { "translation": "type your answer…" },
|
||||||
|
"prompt.note.placeholder": { "translation": "+ note (optional)" },
|
||||||
|
"prompt.chatInstead": { "translation": "chat instead" },
|
||||||
|
"tool.edit.before": { "translation": "— before" },
|
||||||
|
"tool.edit.after": { "translation": "+ after" },
|
||||||
|
"tool.bash.background": { "translation": "background" },
|
||||||
|
"permissionControl.semantics": { "translation": "permission mode: {mode}. Activate to change." },
|
||||||
|
"permissionControl.tooltip": { "translation": "Permission mode: {mode} — change (Ctrl/Cmd+M cycles)" },
|
||||||
|
"permissionBadge.tooltip": { "translation": "Permission mode: {mode}. Click to cycle default/acceptEdits/plan; Shift-click for bypassPermissions." },
|
||||||
|
"permissionBadge.semantics": { "translation": "Permission mode: {label}" },
|
||||||
|
"card.expand": { "translation": "Expand" },
|
||||||
|
"card.collapse": { "translation": "Collapse" },
|
||||||
|
"card.succeeded": { "translation": "succeeded" },
|
||||||
|
"card.failed": { "translation": "failed" },
|
||||||
|
"card.copy": { "translation": "copy" },
|
||||||
|
"taskDock.summary": { "translation": "{count} {tasks} · {done} done" },
|
||||||
|
"taskDock.task.singular": { "translation": "task" },
|
||||||
|
"taskDock.task.plural": { "translation": "tasks" },
|
||||||
|
"taskDock.collapse": { "translation": "Collapse tasks" },
|
||||||
|
"taskDock.expand": { "translation": "Expand tasks" },
|
||||||
|
"taskDock.semantics": { "translation": "Claude task list, {summary}, {state}" },
|
||||||
|
"taskDock.state.expanded": { "translation": "expanded" },
|
||||||
|
"taskDock.state.collapsed": { "translation": "collapsed" },
|
||||||
|
"taskDock.status.done": { "translation": "done" },
|
||||||
|
"taskDock.status.inProgress": { "translation": "in progress" },
|
||||||
|
"taskDock.status.pending": { "translation": "pending" },
|
||||||
|
"taskDock.row.semantics": { "translation": "{text}, {status}" },
|
||||||
|
"sessionPicker.title": { "translation": "Resume a Claude session" },
|
||||||
|
"sessionPicker.empty": { "translation": "No sessions found for this workspace." },
|
||||||
|
"modelPicker.cancel": { "translation": "cancel" },
|
||||||
|
"image.semantics": { "translation": "Image {name}" },
|
||||||
|
"activity.section.session": { "translation": "SESSION" },
|
||||||
|
"activity.control.clear": { "translation": "clear" },
|
||||||
|
"activity.control.compact": { "translation": "compact" },
|
||||||
|
"activity.control.fork": { "translation": "fork" },
|
||||||
|
"activity.control.resume": { "translation": "resume" },
|
||||||
|
"activity.control.refreshUsage": { "translation": "refresh usage" },
|
||||||
|
"activity.control.semantics": { "translation": "{label} session" },
|
||||||
|
"activity.control.tooltip": { "translation": "{label} · {command}" },
|
||||||
|
"activity.empty": { "translation": "No activity recorded yet." },
|
||||||
|
"activity.section.workflows": { "translation": "WORKFLOWS" },
|
||||||
|
"activity.workflow.fallback": { "translation": "workflow" },
|
||||||
|
"activity.workflow.done": { "translation": "done" },
|
||||||
|
"activity.workflow.starting": { "translation": "starting" },
|
||||||
|
"activity.section.usage": { "translation": "USAGE" },
|
||||||
|
"activity.row.session": { "translation": "session" },
|
||||||
|
"activity.row.weekAll": { "translation": "week (all)" },
|
||||||
|
"activity.row.weekSonnet": { "translation": "week (sonnet)" },
|
||||||
|
"activity.section.today": { "translation": "TODAY" },
|
||||||
|
"activity.row.messages": { "translation": "messages" },
|
||||||
|
"activity.row.sessions": { "translation": "sessions" },
|
||||||
|
"activity.row.toolCalls": { "translation": "tool calls" },
|
||||||
|
"activity.section.lifetime": { "translation": "LIFETIME" },
|
||||||
|
"activity.section.runtime": { "translation": "RUNTIME · primary" },
|
||||||
|
"activity.row.model": { "translation": "model" },
|
||||||
|
"activity.row.effort": { "translation": "effort" },
|
||||||
|
"activity.row.context": { "translation": "context" },
|
||||||
|
"activity.row.mode": { "translation": "mode" },
|
||||||
|
"activity.row.skills": { "translation": "skills" },
|
||||||
|
"config.empty": { "translation": "Claude environment not loaded." },
|
||||||
|
"config.section.settings": { "translation": "SETTINGS" },
|
||||||
|
"config.row.model": { "translation": "model" },
|
||||||
|
"config.row.effort": { "translation": "effort" },
|
||||||
|
"config.row.permissionMode": { "translation": "permission mode" },
|
||||||
|
"config.row.outputStyle": { "translation": "output style" },
|
||||||
|
"config.row.source": { "translation": "source" },
|
||||||
|
"config.row.source.value": { "translation": "~/.claude + .claude" },
|
||||||
|
"config.footer": { "translation": "expand a list to see all · click a skill/agent/command → opens its .md" },
|
||||||
|
"config.section.skills": { "translation": "SKILLS" },
|
||||||
|
"config.section.agents": { "translation": "AGENTS" },
|
||||||
|
"config.section.commands": { "translation": "COMMANDS" },
|
||||||
|
"config.section.hooks": { "translation": "HOOKS" },
|
||||||
|
"config.section.permissions": { "translation": "PERMISSIONS" },
|
||||||
|
"config.section.mcpServers": { "translation": "MCP SERVERS" },
|
||||||
|
"config.perm.allow": { "translation": "allow" },
|
||||||
|
"config.perm.ask": { "translation": "ask" },
|
||||||
|
"config.perm.deny": { "translation": "deny" },
|
||||||
|
"config.control.semantics": { "translation": "{label}: {value}. Click to change." },
|
||||||
|
"config.control.tooltip": { "translation": "change {label}" },
|
||||||
|
"config.control.option.semantics": { "translation": "{label}: {name}" },
|
||||||
|
"roster.bypass.confirmBody": { "translation": "Enable bypassPermissions? All tool calls will be auto-allowed." },
|
||||||
|
"roster.bypass.confirm.semantics": { "translation": "Confirm bypass" },
|
||||||
|
"roster.bypass.confirm.tooltip": { "translation": "Confirm" },
|
||||||
|
"roster.bypass.ok": { "translation": "OK" },
|
||||||
|
"roster.bypass.cancel.semantics": { "translation": "Cancel bypass" },
|
||||||
|
"roster.bypass.cancel.tooltip": { "translation": "Cancel" },
|
||||||
|
"roster.bypass.cancel": { "translation": "Cancel" },
|
||||||
|
"roster.hidePane": { "translation": "Hide pane" },
|
||||||
|
"roster.showPane": { "translation": "Show pane" },
|
||||||
|
"roster.unmute": { "translation": "Unmute messages" },
|
||||||
|
"roster.mute": { "translation": "Mute messages" },
|
||||||
|
"roster.inject": { "translation": "Inject message" },
|
||||||
|
"roster.fork": { "translation": "Fork session" },
|
||||||
|
"roster.close": { "translation": "Close session" },
|
||||||
|
"roster.inject.cancel": { "translation": "Cancel" },
|
||||||
|
"taskRow.reassign": { "translation": "Reassign task" },
|
||||||
|
"team.empty": { "translation": "No team active." },
|
||||||
|
"team.section.tasks": { "translation": "TASKS" },
|
||||||
|
"tabStrip.activity": { "translation": "Activity" },
|
||||||
|
"tabStrip.team": { "translation": "Team" },
|
||||||
|
"tabStrip.team.count": { "translation": "Team · {count}" },
|
||||||
|
"tabStrip.config": { "translation": "Config" },
|
||||||
|
"teamChat.section.messages": { "translation": "MESSAGES" },
|
||||||
|
"teamChat.popOut.semantics": { "translation": "Open full chat pane" },
|
||||||
|
"teamChat.popOut.tooltip": { "translation": "Open full chat" },
|
||||||
|
"teamChat.empty": { "translation": "No messages yet." },
|
||||||
|
"teamChat.composer.placeholder": { "translation": "@name or @team …" },
|
||||||
|
"teamChat.pane.title": { "translation": "Team Chat" },
|
||||||
|
"teamChat.interrupt.semantics": { "translation": "Interrupt target session" },
|
||||||
|
"teamChat.interrupt.label": { "translation": "Interrupt" },
|
||||||
|
"command.newSecondary": { "translation": "Claude: open a secondary session" },
|
||||||
|
"command.killAllSessions": { "translation": "Claude: kill all sessions for this repo" },
|
||||||
|
"command.sessionStorage": { "translation": "Claude: session storage (disk usage + cleanup)" },
|
||||||
|
"command.activity.foldLevel": { "translation": "Claude: cycle activity fold level" },
|
||||||
|
"command.agent.show": { "translation": "Claude: show an agent session pane" },
|
||||||
|
"command.agent.hide": { "translation": "Claude: hide an agent session pane" },
|
||||||
|
"command.agent.close": { "translation": "Claude: close (kill) an agent session" },
|
||||||
|
"command.agent.mute": { "translation": "Claude: mute broker delivery to an agent session" },
|
||||||
|
"command.agent.unmute": { "translation": "Claude: unmute broker delivery to an agent session" },
|
||||||
|
"command.agent.injectMessage": { "translation": "Claude: inject a text turn into an agent session" },
|
||||||
|
"command.agent.setPermissionMode": { "translation": "Claude: set permission mode for an agent session" },
|
||||||
|
"command.mode.cycle": { "translation": "Claude: Cycle permission mode" },
|
||||||
|
"command.task.reassign": { "translation": "Claude: reassign a shared task to an agent" },
|
||||||
|
"command.teamChat.open": { "translation": "Claude: open the team chat pane" },
|
||||||
|
"command.teamChat.post": { "translation": "Claude: post a message into the team channel as the user" },
|
||||||
|
"command.agent.fork": { "translation": "Claude: fork a managed session into a new branch session" },
|
||||||
|
"settings.activity.title": { "translation": "Activity" },
|
||||||
|
"settings.activity.conversation.label": { "translation": "Conversation" },
|
||||||
|
"settings.activity.foldLevel.label": { "translation": "Fold level" },
|
||||||
|
"settings.activity.foldLevel.help": { "translation": "How aggressively the conversation folds tool calls, thinking, and results." },
|
||||||
|
"settings.activity.opt.none": { "translation": "Show everything" },
|
||||||
|
"settings.activity.opt.tools": { "translation": "Fold tool calls" },
|
||||||
|
"settings.activity.opt.thinking": { "translation": "Fold tools + thinking" },
|
||||||
|
"settings.activity.opt.everything": { "translation": "Fold all but prose" },
|
||||||
|
"settings.claude.title": { "translation": "Claude" },
|
||||||
|
"settings.claude.newSessionDefaults.label": { "translation": "New session defaults" },
|
||||||
|
"settings.claude.model.label": { "translation": "Model" },
|
||||||
|
"settings.claude.model.help": { "translation": "Model for new sessions." },
|
||||||
|
"settings.claude.effort.label": { "translation": "Effort" },
|
||||||
|
"settings.claude.effort.help": { "translation": "Reasoning effort for new sessions (applied via --effort at spawn)." },
|
||||||
|
"settings.claude.permissionMode.label": { "translation": "Permission mode" },
|
||||||
|
"settings.claude.permissionMode.help": { "translation": "Starting permission mode for new sessions." }
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"command.clide.installCli": { "translation": "clide: Install 'clide' command in PATH" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Decisions" },
|
||||||
|
"tab.detail.title": { "translation": "Decision" },
|
||||||
|
"loading": { "translation": "Loading decisions..." },
|
||||||
|
"error.load": { "translation": "failed to load decisions" },
|
||||||
|
"empty": { "translation": "No decisions found.\nRun `pql decisions sync` to index." },
|
||||||
|
"filter.hint": { "translation": "Filter decisions…" },
|
||||||
|
"refresh.tooltip": { "translation": "Refresh decisions" },
|
||||||
|
"section.confirmed": { "translation": "CONFIRMED" },
|
||||||
|
"section.questions": { "translation": "QUESTIONS" },
|
||||||
|
"section.rejected": { "translation": "REJECTED" },
|
||||||
|
"badge.resolved": { "translation": "resolved" },
|
||||||
|
"detail.loading": { "translation": "Loading…" },
|
||||||
|
"detail.empty": { "translation": "Select a decision to view details." },
|
||||||
|
"detail.section.refs": { "translation": "CROSS-REFERENCES" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"dialog.title": { "translation": "Open an external link?" },
|
||||||
|
"dialog.body": { "translation": "A clide:// link from outside the app is asking to:" },
|
||||||
|
"dialog.warning": { "translation": "Only allow this if you trust where the link came from." },
|
||||||
|
"button.cancel": { "translation": "Cancel" },
|
||||||
|
"button.open": { "translation": "Open" },
|
||||||
|
"command.deeplink.invoke": { "translation": "Open a clide:// deep link" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"command.reset": { "translation": "Layout: Reset to Classic" },
|
||||||
|
"preset.classic": { "translation": "Classic" },
|
||||||
|
"command.palette.toggle": { "translation": "Command Palette" },
|
||||||
|
"command.sidebar.collapse": { "translation": "Toggle Sidebar Collapse" },
|
||||||
|
"command.context.collapse": { "translation": "Toggle Context Panel Collapse" },
|
||||||
|
"command.panel.focus.left": { "translation": "Focus Left Panel" },
|
||||||
|
"command.panel.focus.middle": { "translation": "Focus Middle Panel" },
|
||||||
|
"command.panel.focus.right": { "translation": "Focus Right Panel" },
|
||||||
|
"command.panel.focusMode": { "translation": "Toggle Focus Mode" },
|
||||||
|
"command.panel.focusMode.exit": { "translation": "Exit Focus Mode" },
|
||||||
|
"command.editor.open": { "translation": "Open Editor" },
|
||||||
|
"command.editor.close": { "translation": "Close Editor" },
|
||||||
|
"command.workspace.tab.next": { "translation": "Next Workspace Tab" },
|
||||||
|
"command.workspace.tab.previous": { "translation": "Previous Workspace Tab" },
|
||||||
|
"command.sidebar.section.1": { "translation": "Sidebar: Section 1" },
|
||||||
|
"command.sidebar.section.2": { "translation": "Sidebar: Section 2" },
|
||||||
|
"command.sidebar.section.3": { "translation": "Sidebar: Section 3" },
|
||||||
|
"command.sidebar.section.4": { "translation": "Sidebar: Section 4" },
|
||||||
|
"command.sidebar.section.5": { "translation": "Sidebar: Section 5" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Diff" },
|
||||||
|
"view.semantics": { "translation": "diff view" },
|
||||||
|
"status.loading": { "translation": "Loading…" },
|
||||||
|
"empty.staged": { "translation": "No staged changes." },
|
||||||
|
"empty.unstaged": { "translation": "No unstaged changes." },
|
||||||
|
"toolbar.unstaged": { "translation": "Unstaged" },
|
||||||
|
"toolbar.unstaged.semantics": { "translation": "show unstaged changes" },
|
||||||
|
"toolbar.staged": { "translation": "Staged" },
|
||||||
|
"toolbar.staged.semantics": { "translation": "show staged changes" },
|
||||||
|
"meta.newFile": { "translation": "new file" },
|
||||||
|
"meta.deleted": { "translation": "deleted" },
|
||||||
|
"meta.renamedFrom": { "translation": "renamed from {path}" },
|
||||||
|
"meta.binary": { "translation": "binary" }
|
||||||
|
}
|
||||||
+3
-1
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"tab.title": { "translation": "Editor" },
|
"tab.title": { "translation": "Editor" },
|
||||||
|
"chrome.title": { "translation": "editor" },
|
||||||
"empty": { "translation": "Open a file to begin editing." },
|
"empty": { "translation": "Open a file to begin editing." },
|
||||||
"subtitle.no-buffer": { "translation": "no buffer · use `clide open <path>` or pick a file in the tree" }
|
"subtitle.no-buffer": { "translation": "no buffer · use `clide open <path>` or pick a file in the tree" },
|
||||||
|
"a11y.text-area": { "translation": "editor text area" }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"notice.title": { "translation": "Extension management is coming" },
|
||||||
|
"notice.body": { "translation": "Installing, enabling, and disabling extensions arrives with third-party (Lua) extension support. For now the built-in extensions are always on." },
|
||||||
|
"notice.tracked": { "translation": "Tracked in T-8 (Tier 6) · D-16" },
|
||||||
|
"settings.extensions.title": { "translation": "Extensions" },
|
||||||
|
"settings.extensions.section.notice": { "translation": "" },
|
||||||
|
"settings.extensions.field.notice.label": { "translation": "" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Files" },
|
||||||
|
"loading": { "translation": "Loading…" },
|
||||||
|
"empty": { "translation": "No visible files" },
|
||||||
|
"filter.hint": { "translation": "Filter files…" },
|
||||||
|
"a11y.tree": { "translation": "file tree — {name}" },
|
||||||
|
"a11y.collapse": { "translation": "Collapse {name}" },
|
||||||
|
"a11y.expand": { "translation": "Expand {name}" },
|
||||||
|
"a11y.open": { "translation": "Open {name}" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Git" },
|
||||||
|
"panel.semantics": { "translation": "git panel" },
|
||||||
|
"filter.hint": { "translation": "Filter changes…" },
|
||||||
|
"status.loading": { "translation": "Loading…" },
|
||||||
|
"status.clean": { "translation": "Nothing to commit, working tree clean." },
|
||||||
|
"group.conflicts": { "translation": "Merge conflicts" },
|
||||||
|
"group.staged": { "translation": "Staged" },
|
||||||
|
"group.changes": { "translation": "Changes" },
|
||||||
|
"group.untracked": { "translation": "Untracked" },
|
||||||
|
"action.unstageAll": { "translation": "Unstage all" },
|
||||||
|
"action.stageAll": { "translation": "Stage all" },
|
||||||
|
"commit.message.semantics": { "translation": "commit message" },
|
||||||
|
"commit.button": { "translation": "Commit" },
|
||||||
|
"commit.button.semantics": { "translation": "commit staged changes" },
|
||||||
|
"branch.detached": { "translation": "(detached)" },
|
||||||
|
"action.pull": { "translation": "Pull" },
|
||||||
|
"action.pull.semantics": { "translation": "git pull" },
|
||||||
|
"action.push": { "translation": "Push" },
|
||||||
|
"action.push.semantics": { "translation": "git push" },
|
||||||
|
"state.added": { "translation": "added" },
|
||||||
|
"state.modified": { "translation": "modified" },
|
||||||
|
"state.deleted": { "translation": "deleted" },
|
||||||
|
"state.renamed": { "translation": "renamed" },
|
||||||
|
"state.copied": { "translation": "copied" },
|
||||||
|
"state.untracked": { "translation": "untracked" },
|
||||||
|
"row.stage.semantics": { "translation": "stage {name}" },
|
||||||
|
"row.unstage.semantics": { "translation": "unstage {name}" },
|
||||||
|
"row.discard.semantics": { "translation": "discard changes to {name}" },
|
||||||
|
"discard.title": { "translation": "Discard changes?" },
|
||||||
|
"discard.body": { "translation": "Unstaged changes to {name} will be permanently lost." },
|
||||||
|
"button.cancel": { "translation": "Cancel" },
|
||||||
|
"button.discard": { "translation": "Discard" },
|
||||||
|
"branch.switch.semantics": { "translation": "switch branch — {branch}" },
|
||||||
|
"branchPicker.title": { "translation": "Switch branch" },
|
||||||
|
"branchPicker.empty": { "translation": "No branches found." },
|
||||||
|
"branchPicker.loadFailed": { "translation": "failed to load branches" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"command.preset.default": { "translation": "Keymap: Default" },
|
||||||
|
"command.preset.vim": { "translation": "Keymap: Vim" },
|
||||||
|
"command.preset.vscode": { "translation": "Keymap: VS Code" },
|
||||||
|
"command.preset.jetbrains": { "translation": "Keymap: JetBrains" },
|
||||||
|
"settings.keymap.title": { "translation": "Keymap" },
|
||||||
|
"settings.keymap.section.preset": { "translation": "Preset" },
|
||||||
|
"settings.keymap.field.preset.label": { "translation": "Active preset" },
|
||||||
|
"settings.keymap.field.preset.help": { "translation": "Keyboard layout for the whole app." },
|
||||||
|
"settings.keymap.field.preset.option.default": { "translation": "Default" },
|
||||||
|
"settings.keymap.field.preset.option.vim": { "translation": "Vim" },
|
||||||
|
"settings.keymap.field.preset.option.vscode": { "translation": "VS Code" },
|
||||||
|
"settings.keymap.field.preset.option.jetbrains": { "translation": "JetBrains" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"empty": { "translation": "Select a .md file to preview it here." },
|
||||||
|
"chrome.title": { "translation": "viewer" },
|
||||||
|
"subtitle.lines": { "translation": "{count} lines" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"menu.file": { "translation": "File" },
|
||||||
|
"menu.view": { "translation": "View" },
|
||||||
|
"menu.help": { "translation": "Help" },
|
||||||
|
"about.version": { "translation": "Version" },
|
||||||
|
"about.commit": { "translation": "Commit" },
|
||||||
|
"about.built": { "translation": "Built" },
|
||||||
|
"about.repository": { "translation": "Repository" },
|
||||||
|
"licenses.heading": { "translation": "Bundled dependencies" },
|
||||||
|
"licenses.unavailable": { "translation": "Licenses unavailable." },
|
||||||
|
"licenses.loading": { "translation": "Loading…" },
|
||||||
|
"button.close": { "translation": "Close" },
|
||||||
|
"button.cancel": { "translation": "Cancel" },
|
||||||
|
"button.open": { "translation": "Open" },
|
||||||
|
"button.opening": { "translation": "Opening…" },
|
||||||
|
"button.ok": { "translation": "OK" },
|
||||||
|
"dialog.openProject.title": { "translation": "Open project" },
|
||||||
|
"dialog.openProject.body": { "translation": "Enter the path to a git repository." },
|
||||||
|
"dialog.openProject.error": { "translation": "Not a git repository" },
|
||||||
|
"dialog.notRepo.title": { "translation": "No git repo found" },
|
||||||
|
"dialog.notRepo.body": { "translation": "A clide project root requires a git repository." },
|
||||||
|
"command.file.openFolder": { "translation": "File: Open Folder…" },
|
||||||
|
"command.file.newWindow": { "translation": "File: New Window" },
|
||||||
|
"command.file.closeWorkspace": { "translation": "File: Close Project" },
|
||||||
|
"command.help.about": { "translation": "Help: About clide" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"a11y.log": { "translation": "output log" },
|
||||||
|
"empty": { "translation": "No output yet." },
|
||||||
|
"empty.filtered": { "translation": "No output matches the filter." },
|
||||||
|
"filter.hint": { "translation": "Filter…" },
|
||||||
|
"chip.level": { "translation": "Level: {level}" },
|
||||||
|
"chip.source": { "translation": "Source: {source}" },
|
||||||
|
"chip.clear": { "translation": "Clear" },
|
||||||
|
"source.all": { "translation": "all" },
|
||||||
|
"a11y.jumpToLatest": { "translation": "jump to latest" },
|
||||||
|
"jump.label": { "translation": "Jump to latest ↓" },
|
||||||
|
"a11y.toggleDock": { "translation": "toggle output dock" },
|
||||||
|
"dock.label": { "translation": "Output" },
|
||||||
|
"command.dock.toggle": { "translation": "Toggle output dock" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "pql" },
|
||||||
|
"tab.links.title": { "translation": "Links" },
|
||||||
|
"filter.markdown.hint": { "translation": "Filter markdown…" },
|
||||||
|
"loading": { "translation": "Loading…" },
|
||||||
|
"empty.markdown": { "translation": "No markdown files found." },
|
||||||
|
"search.vault.hint": { "translation": "Search vault…" },
|
||||||
|
"search.query.hint": { "translation": "PQL query…" },
|
||||||
|
"backlinks.empty": { "translation": "Open a file to see its links." },
|
||||||
|
"backlinks.loading": { "translation": "Loading…" },
|
||||||
|
"backlinks.semantics": { "translation": "backlinks for {path}" },
|
||||||
|
"group.backlinks": { "translation": "Backlinks" },
|
||||||
|
"group.outlinks": { "translation": "Outlinks" },
|
||||||
|
"group.label": { "translation": "{label} ({count})" },
|
||||||
|
"group.none": { "translation": "None" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Problems" },
|
||||||
|
"semantics.panel": { "translation": "problems panel" },
|
||||||
|
"filter.hint": { "translation": "Filter problems…" },
|
||||||
|
"count": { "translation": "Problems ({count})" },
|
||||||
|
"refresh.label": { "translation": "Refresh" },
|
||||||
|
"refresh.semantics": { "translation": "refresh problems" },
|
||||||
|
"scanning": { "translation": "Scanning…" },
|
||||||
|
"empty": { "translation": "No problems found." }
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"mode.find": { "translation": "Find" },
|
||||||
|
"mode.vault": { "translation": "Vault" },
|
||||||
|
"mode.query": { "translation": "Query" },
|
||||||
|
"mode.markdown": { "translation": "Markdown" },
|
||||||
|
"find.hint": { "translation": "Search" },
|
||||||
|
"replace.hint": { "translation": "Replace" },
|
||||||
|
"include.hint": { "translation": "files to include (e.g. *.dart)" },
|
||||||
|
"exclude.hint": { "translation": "files to exclude" },
|
||||||
|
"toggle.regex": { "translation": "Regular expression" },
|
||||||
|
"toggle.caseInsensitive": { "translation": "Case insensitive" },
|
||||||
|
"status.searching": { "translation": "Searching…" },
|
||||||
|
"status.noResults": { "translation": "No results" },
|
||||||
|
"status.counts": { "translation": "{matches} in {files}" },
|
||||||
|
"a11y.openMatch": { "translation": "Open {path} line {line}" },
|
||||||
|
"button.replaceAll": { "translation": "Replace all" },
|
||||||
|
"button.ok": { "translation": "OK" },
|
||||||
|
"button.cancel": { "translation": "Cancel" },
|
||||||
|
"button.confirm": { "translation": "Confirm" },
|
||||||
|
"dialog.dirty.title": { "translation": "Working tree not clean" },
|
||||||
|
"dialog.dirty.body": { "translation": "Commit or stash your changes before replacing — git is the only undo." },
|
||||||
|
"dialog.confirm.body": { "translation": "Replace {matches} match(es) across {files} file(s)? This cannot be undone in clide." }
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"command.open": { "translation": "Settings…" },
|
||||||
|
"modal.title": { "translation": "Settings" },
|
||||||
|
"modal.close": { "translation": "Close" },
|
||||||
|
"modal.close.hint": { "translation": "Close settings without changing anything" },
|
||||||
|
"rail.header": { "translation": "Categories" },
|
||||||
|
"panel.empty": { "translation": "No settings categories are registered yet." },
|
||||||
|
"search.hint": { "translation": "Search settings…" },
|
||||||
|
"search.empty": { "translation": "No settings match your search." },
|
||||||
|
"scope.project": { "translation": "This project" },
|
||||||
|
"scope.always": { "translation": "All clide" },
|
||||||
|
"scope.default": { "translation": "Default" },
|
||||||
|
"scope.reset": { "translation": "Reset to default" },
|
||||||
|
"scope.tip.project": { "translation": "Stored in this project (.clide)" },
|
||||||
|
"scope.tip.always": { "translation": "Stored for all clide (~/.clide)" },
|
||||||
|
"scope.tip.default": { "translation": "Unset — using the default" }
|
||||||
|
}
|
||||||
+3
-1
@@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"tab.title": { "translation": "Terminal" },
|
"tab.title": { "translation": "Terminal" },
|
||||||
|
"chrome.title": { "translation": "terminal" },
|
||||||
"subtitle.spawning": { "translation": "spawning shell…" },
|
"subtitle.spawning": { "translation": "spawning shell…" },
|
||||||
"subtitle.exited": { "translation": "Shell exited." },
|
"subtitle.exited": { "translation": "Shell exited." },
|
||||||
"error.unavailable": { "translation": "Terminal unavailable" },
|
"error.unavailable": { "translation": "Terminal unavailable" },
|
||||||
"error.daemon": { "translation": "Backend not connected." }
|
"error.daemon": { "translation": "Backend not connected." },
|
||||||
|
"a11y.label": { "translation": "terminal — {subtitle}" }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"command.pick": { "translation": "Theme…" },
|
||||||
|
"modal.title": { "translation": "Select theme" },
|
||||||
|
"modal.cancel": { "translation": "Cancel" },
|
||||||
|
"modal.cancel.hint": { "translation": "Close the theme picker without changing the current theme" },
|
||||||
|
"row.select.hint": { "translation": "Activate this theme" },
|
||||||
|
"section.appearance": { "translation": "Appearance" },
|
||||||
|
"toggle.highContrast": { "translation": "High contrast" },
|
||||||
|
"settings.appearance.title": { "translation": "Appearance" },
|
||||||
|
"settings.appearance.section.theme": { "translation": "Theme" },
|
||||||
|
"settings.appearance.section.typography": { "translation": "Typography" },
|
||||||
|
"settings.appearance.field.theme.label": { "translation": "Theme" },
|
||||||
|
"settings.appearance.field.theme.help": { "translation": "Color theme; high contrast switches to the accessible variant." },
|
||||||
|
"settings.appearance.field.uiFont.label": { "translation": "UI font" },
|
||||||
|
"settings.appearance.field.uiFont.help": { "translation": "Typeface for the app interface; applies live." },
|
||||||
|
"settings.appearance.field.uiFont.option.josefinSans": { "translation": "Josefin Sans" },
|
||||||
|
"settings.appearance.field.uiFont.option.inter": { "translation": "Inter" },
|
||||||
|
"settings.appearance.field.monoFont.label": { "translation": "Monospace font" },
|
||||||
|
"settings.appearance.field.monoFont.help": { "translation": "Terminal, diffs, code, and IDs; applies live." },
|
||||||
|
"settings.appearance.field.monoFont.option.jetBrainsMono": { "translation": "JetBrains Mono" },
|
||||||
|
"settings.appearance.field.monoFont.option.firaMono": { "translation": "Fira Mono" },
|
||||||
|
"settings.appearance.section.language": { "translation": "Language" },
|
||||||
|
"settings.appearance.field.language.label": { "translation": "Language" },
|
||||||
|
"settings.appearance.field.language.help": { "translation": "Language for the app interface; applies live." }
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Tickets" },
|
||||||
|
"tab.detail.title": { "translation": "Ticket" },
|
||||||
|
"type.initiative": { "translation": "Initiative" },
|
||||||
|
"type.epic": { "translation": "Epic" },
|
||||||
|
"type.story": { "translation": "Story" },
|
||||||
|
"type.task": { "translation": "Task" },
|
||||||
|
"type.bug": { "translation": "Bug" },
|
||||||
|
"loading": { "translation": "Loading tickets..." },
|
||||||
|
"error.load": { "translation": "failed to load tickets" },
|
||||||
|
"empty": { "translation": "No tickets.\nRun `pql ticket new` to create one." },
|
||||||
|
"filter.hint": { "translation": "Filter tickets…" },
|
||||||
|
"refresh.tooltip": { "translation": "Refresh tickets" },
|
||||||
|
"section.in_progress": { "translation": "IN PROGRESS" },
|
||||||
|
"section.review": { "translation": "REVIEW" },
|
||||||
|
"section.ready": { "translation": "READY" },
|
||||||
|
"section.backlog": { "translation": "BACKLOG" },
|
||||||
|
"section.done": { "translation": "DONE" },
|
||||||
|
"section.cancelled": { "translation": "CANCELLED" },
|
||||||
|
"chip.label": { "translation": "{type} type filter" },
|
||||||
|
"chip.tooltip": { "translation": "Click to toggle · double-click to isolate" },
|
||||||
|
"badge.wip": { "translation": "WIP" },
|
||||||
|
"badge.review": { "translation": "REVIEW" },
|
||||||
|
"badge.cancelled": { "translation": "CANCELLED" },
|
||||||
|
"pickUp.tooltip": { "translation": "Pick up — hand this ticket to the Claude pane" },
|
||||||
|
"detail.loading": { "translation": "Loading…" },
|
||||||
|
"detail.empty": { "translation": "Select a ticket to view details." },
|
||||||
|
"detail.assigned": { "translation": "assigned: {name}" },
|
||||||
|
"detail.section.parents": { "translation": "PARENT TREE" },
|
||||||
|
"detail.section.decisions": { "translation": "REFERENCED DECISIONS" },
|
||||||
|
"status.backlog": { "translation": "BACKLOG" },
|
||||||
|
"status.ready": { "translation": "READY" },
|
||||||
|
"status.in_progress": { "translation": "WIP" },
|
||||||
|
"status.review": { "translation": "REVIEW" },
|
||||||
|
"status.done": { "translation": "DONE" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"command.view.zoomIn": { "translation": "View: Zoom In" },
|
||||||
|
"command.view.zoomOut": { "translation": "View: Zoom Out" },
|
||||||
|
"command.view.zoomReset": { "translation": "View: Reset Zoom" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"mode.normal": { "translation": "NORMAL" },
|
||||||
|
"mode.insert": { "translation": "INSERT" },
|
||||||
|
"mode.visual": { "translation": "VISUAL" },
|
||||||
|
"command.vim.mode.normal": { "translation": "Vim: Normal mode" },
|
||||||
|
"command.vim.mode.insert": { "translation": "Vim: Insert mode" },
|
||||||
|
"command.vim.mode.visual": { "translation": "Vim: Visual mode" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"title": { "translation": "clide" },
|
||||||
|
"subtitle": { "translation": "IDE for Claude Code CLI" },
|
||||||
|
"open-project": { "translation": "Open project" },
|
||||||
|
"open-project.hint": { "translation": "Pick a git repository to open as the workspace" },
|
||||||
|
"tab.title": { "translation": "Welcome" },
|
||||||
|
"section.tips": { "translation": "TIPS" },
|
||||||
|
"section.start": { "translation": "START" },
|
||||||
|
"section.recent": { "translation": "RECENT" },
|
||||||
|
"tips.quickOpen": { "translation": "Quick open" },
|
||||||
|
"tips.commandPalette": { "translation": "Command palette" },
|
||||||
|
"tips.toggleSidebar": { "translation": "Toggle sidebar" },
|
||||||
|
"tips.toggleContext": { "translation": "Toggle context" },
|
||||||
|
"tips.findInFiles": { "translation": "Find in files" },
|
||||||
|
"tips.focusMode": { "translation": "Focus mode" },
|
||||||
|
"action.openFolder": { "translation": "Open folder…" },
|
||||||
|
"recent.empty": { "translation": "No recent projects." },
|
||||||
|
"sticky.label": { "translation": "always open this project on launch" },
|
||||||
|
"sticky.tooltip": { "translation": "Always open this project on launch" },
|
||||||
|
"sticky.tooltip.active": { "translation": "Always open this project on launch (uncheck to restore picker)" },
|
||||||
|
"status.checking": { "translation": "checking…" },
|
||||||
|
"status.ok": { "translation": "application ok" },
|
||||||
|
"status.notFound": { "translation": "{tool} not found" },
|
||||||
|
"status.theme": { "translation": "theme: " },
|
||||||
|
"dialog.openProject.title": { "translation": "Open project" },
|
||||||
|
"dialog.openProject.body": { "translation": "Enter the path to a git repository." },
|
||||||
|
"dialog.openProject.error": { "translation": "Not a git repository" },
|
||||||
|
"button.cancel": { "translation": "Cancel" },
|
||||||
|
"button.open": { "translation": "Open" },
|
||||||
|
"button.opening": { "translation": "Opening…" },
|
||||||
|
"button.ok": { "translation": "OK" },
|
||||||
|
"dialog.notRepo.title": { "translation": "No git repo found" },
|
||||||
|
"dialog.notRepo.body": { "translation": "A clide project root requires a git repository." },
|
||||||
|
"command.workspace.open-project": { "translation": "Workspace: Open project…" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"collapser.expand": { "translation": "Expand" },
|
||||||
|
"collapser.collapse": { "translation": "Collapse" },
|
||||||
|
"collapser.expanded": { "translation": "expanded" },
|
||||||
|
"collapser.collapsed": { "translation": "collapsed" },
|
||||||
|
"toast.dismiss": { "translation": "Dismiss notification" },
|
||||||
|
"lightbox.close": { "translation": "close" },
|
||||||
|
"lightbox.hint": { "translation": "scroll to zoom · double-click to reset · Esc to close" },
|
||||||
|
"tab.new": { "translation": "New tab" },
|
||||||
|
"exline.notCommand": { "translation": "Not an editor command" },
|
||||||
|
"spine.expandSuffix": { "translation": "click to expand" },
|
||||||
|
"pane.close": { "translation": "Close pane" },
|
||||||
|
"pane.header": { "translation": "pane header: {title}" },
|
||||||
|
"reader.back": { "translation": "Back" },
|
||||||
|
"reader.forward": { "translation": "Forward" },
|
||||||
|
"reader.jumpToPin": { "translation": "Jump to pin" },
|
||||||
|
"reader.edit": { "translation": "Edit in editor" },
|
||||||
|
"reader.pin": { "translation": "Pin" },
|
||||||
|
"reader.unpin": { "translation": "Unpin" },
|
||||||
|
"link.openInEditor": { "translation": "Open in editor" },
|
||||||
|
"resize.axis.width": { "translation": "width" },
|
||||||
|
"resize.axis.height": { "translation": "height" },
|
||||||
|
"resize.sidebar": { "translation": "Sidebar {axis}" },
|
||||||
|
"resize.contextPanel": { "translation": "Context panel {axis}" },
|
||||||
|
"resize.slot": { "translation": "{slot} {axis}" },
|
||||||
|
"resize.pixels": { "translation": "{n} pixels" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Claude" },
|
||||||
|
"status.attaching": { "translation": "verbinden…" },
|
||||||
|
"status.no-tmux": { "translation": "no-tmux · elke start opnieuw" },
|
||||||
|
"status.exited": { "translation": "sessie beëindigd" },
|
||||||
|
"status.primary-exited": { "translation": "sessie beëindigd — herstart clide om opnieuw te proberen" },
|
||||||
|
"banner.title": { "translation": "Claude" },
|
||||||
|
"banner.warmingUp": { "translation": "Opstarten — je gesprek verschijnt hier." },
|
||||||
|
"composer.hint": { "translation": "Bericht aan Claude… (Enter om te verzenden · Shift+Enter voor een nieuwe regel)" },
|
||||||
|
"composer.stop": { "translation": "Stop ⎋" },
|
||||||
|
"composer.stop.hint": { "translation": "Onderbreek de lopende beurt (Escape)" },
|
||||||
|
"composer.removeAttachment": { "translation": "{name} verwijderen" },
|
||||||
|
"pane.starting": { "translation": "starten…" },
|
||||||
|
"pane.modeBadge.semantics": { "translation": "permissiemodus: {mode}" },
|
||||||
|
"running.semantics": { "translation": "Claude is bezig" },
|
||||||
|
"conversation.empty": { "translation": "Wachten op Claude…" },
|
||||||
|
"conversation.label.you": { "translation": "jij" },
|
||||||
|
"conversation.label.claude": { "translation": "claude" },
|
||||||
|
"conversation.label.clide": { "translation": "clide" },
|
||||||
|
"conversation.label.agent": { "translation": "agent" },
|
||||||
|
"conversation.label.agentPrompt": { "translation": "agent-prompt" },
|
||||||
|
"conversation.label.context": { "translation": "context" },
|
||||||
|
"conversation.label.thinking": { "translation": "nadenken" },
|
||||||
|
"conversation.label.agentThinking": { "translation": "agent denkt na" },
|
||||||
|
"conversation.label.image": { "translation": "afbeelding" },
|
||||||
|
"conversation.label.agentRun": { "translation": "agent-uitvoering" },
|
||||||
|
"conversation.label.workflow": { "translation": "workflow" },
|
||||||
|
"conversation.label.error": { "translation": "fout" },
|
||||||
|
"conversation.label.result": { "translation": "resultaat" },
|
||||||
|
"conversation.label.denied": { "translation": "geweigerd" },
|
||||||
|
"conversation.segment.prompt": { "translation": "prompt" },
|
||||||
|
"conversation.segment.result": { "translation": "resultaat" },
|
||||||
|
"conversation.segment.liveTail": { "translation": "live tail" },
|
||||||
|
"conversation.segment.usage": { "translation": "verbruik" },
|
||||||
|
"conversation.segment.script": { "translation": "script" },
|
||||||
|
"conversation.workflow.launching": { "translation": "Starten…" },
|
||||||
|
"conversation.imagePlaceholder": { "translation": "kon {path} niet laden" },
|
||||||
|
"conversation.bashTail.empty": { "translation": "geen onafhankelijke bron om te volgen" },
|
||||||
|
"conversation.bashTail.label": { "translation": "live tail" },
|
||||||
|
"conversation.cluster.activity": { "translation": "Activiteit" },
|
||||||
|
"conversation.cluster.edits": { "translation": "Wijzigingen" },
|
||||||
|
"prompt.permission.allow": { "translation": "1. Toestaan" },
|
||||||
|
"prompt.permission.allowRemember": { "translation": "2. Toestaan en niet meer vragen" },
|
||||||
|
"prompt.permission.deny": { "translation": "{n}. Weigeren" },
|
||||||
|
"prompt.permission.denySimplify": { "translation": "{n}. Weigeren en vereenvoudigen" },
|
||||||
|
"prompt.permission.denySimplify.tooltip": { "translation": "Weiger en vraag Claude deze actie in een eenvoudigere vorm opnieuw te proberen — complexe interacties werken niet goed met het permissiesysteem." },
|
||||||
|
"prompt.permission.note.placeholder": { "translation": "voeg een notitie toe (optioneel) — wordt naar Claude gestuurd" },
|
||||||
|
"prompt.permission.label": { "translation": "permissie · {name}" },
|
||||||
|
"prompt.question.label": { "translation": "vraag" },
|
||||||
|
"prompt.review.label": { "translation": "controleren" },
|
||||||
|
"prompt.review.title": { "translation": "Controleer je antwoorden" },
|
||||||
|
"prompt.submit": { "translation": "Verzenden" },
|
||||||
|
"prompt.submitAnswers": { "translation": "Antwoorden verzenden" },
|
||||||
|
"prompt.back": { "translation": "‹ Terug" },
|
||||||
|
"prompt.next": { "translation": "Volgende ›" },
|
||||||
|
"prompt.reviewNav": { "translation": "Controleren ›" },
|
||||||
|
"prompt.nav.review": { "translation": "Controleren" },
|
||||||
|
"prompt.option.other": { "translation": "Anders…" },
|
||||||
|
"prompt.other.placeholder": { "translation": "typ je antwoord…" },
|
||||||
|
"prompt.note.placeholder": { "translation": "+ notitie (optioneel)" },
|
||||||
|
"prompt.chatInstead": { "translation": "in plaats daarvan chatten" },
|
||||||
|
"tool.edit.before": { "translation": "— voor" },
|
||||||
|
"tool.edit.after": { "translation": "+ na" },
|
||||||
|
"tool.bash.background": { "translation": "achtergrond" },
|
||||||
|
"permissionControl.semantics": { "translation": "permissiemodus: {mode}. Activeer om te wijzigen." },
|
||||||
|
"permissionControl.tooltip": { "translation": "Permissiemodus: {mode} — wijzigen (Ctrl/Cmd+M wisselt)" },
|
||||||
|
"permissionBadge.tooltip": { "translation": "Permissiemodus: {mode}. Klik om te wisselen tussen default/acceptEdits/plan; Shift-klik voor bypassPermissions." },
|
||||||
|
"permissionBadge.semantics": { "translation": "Permissiemodus: {label}" },
|
||||||
|
"card.expand": { "translation": "Uitvouwen" },
|
||||||
|
"card.collapse": { "translation": "Invouwen" },
|
||||||
|
"card.succeeded": { "translation": "gelukt" },
|
||||||
|
"card.failed": { "translation": "mislukt" },
|
||||||
|
"card.copy": { "translation": "kopiëren" },
|
||||||
|
"taskDock.summary": { "translation": "{count} {tasks} · {done} klaar" },
|
||||||
|
"taskDock.task.singular": { "translation": "taak" },
|
||||||
|
"taskDock.task.plural": { "translation": "taken" },
|
||||||
|
"taskDock.collapse": { "translation": "Taken invouwen" },
|
||||||
|
"taskDock.expand": { "translation": "Taken uitvouwen" },
|
||||||
|
"taskDock.semantics": { "translation": "Takenlijst van Claude, {summary}, {state}" },
|
||||||
|
"taskDock.state.expanded": { "translation": "uitgevouwen" },
|
||||||
|
"taskDock.state.collapsed": { "translation": "ingevouwen" },
|
||||||
|
"taskDock.status.done": { "translation": "klaar" },
|
||||||
|
"taskDock.status.inProgress": { "translation": "bezig" },
|
||||||
|
"taskDock.status.pending": { "translation": "in wachtrij" },
|
||||||
|
"taskDock.row.semantics": { "translation": "{text}, {status}" },
|
||||||
|
"sessionPicker.title": { "translation": "Een Claude-sessie hervatten" },
|
||||||
|
"sessionPicker.empty": { "translation": "Geen sessies gevonden voor deze workspace." },
|
||||||
|
"modelPicker.cancel": { "translation": "annuleren" },
|
||||||
|
"image.semantics": { "translation": "Afbeelding {name}" },
|
||||||
|
"activity.section.session": { "translation": "SESSIE" },
|
||||||
|
"activity.control.clear": { "translation": "wissen" },
|
||||||
|
"activity.control.compact": { "translation": "compact" },
|
||||||
|
"activity.control.fork": { "translation": "fork" },
|
||||||
|
"activity.control.resume": { "translation": "hervatten" },
|
||||||
|
"activity.control.refreshUsage": { "translation": "verbruik vernieuwen" },
|
||||||
|
"activity.control.semantics": { "translation": "{label} sessie" },
|
||||||
|
"activity.control.tooltip": { "translation": "{label} · {command}" },
|
||||||
|
"activity.empty": { "translation": "Nog geen activiteit vastgelegd." },
|
||||||
|
"activity.section.workflows": { "translation": "WORKFLOWS" },
|
||||||
|
"activity.workflow.fallback": { "translation": "workflow" },
|
||||||
|
"activity.workflow.done": { "translation": "klaar" },
|
||||||
|
"activity.workflow.starting": { "translation": "starten" },
|
||||||
|
"activity.section.usage": { "translation": "VERBRUIK" },
|
||||||
|
"activity.row.session": { "translation": "sessie" },
|
||||||
|
"activity.row.weekAll": { "translation": "week (alles)" },
|
||||||
|
"activity.row.weekSonnet": { "translation": "week (sonnet)" },
|
||||||
|
"activity.section.today": { "translation": "VANDAAG" },
|
||||||
|
"activity.row.messages": { "translation": "berichten" },
|
||||||
|
"activity.row.sessions": { "translation": "sessies" },
|
||||||
|
"activity.row.toolCalls": { "translation": "tool-aanroepen" },
|
||||||
|
"activity.section.lifetime": { "translation": "TOTAAL" },
|
||||||
|
"activity.section.runtime": { "translation": "RUNTIME · primair" },
|
||||||
|
"activity.row.model": { "translation": "model" },
|
||||||
|
"activity.row.effort": { "translation": "effort" },
|
||||||
|
"activity.row.context": { "translation": "context" },
|
||||||
|
"activity.row.mode": { "translation": "modus" },
|
||||||
|
"activity.row.skills": { "translation": "skills" },
|
||||||
|
"config.empty": { "translation": "Claude-omgeving niet geladen." },
|
||||||
|
"config.section.settings": { "translation": "INSTELLINGEN" },
|
||||||
|
"config.row.model": { "translation": "model" },
|
||||||
|
"config.row.effort": { "translation": "effort" },
|
||||||
|
"config.row.permissionMode": { "translation": "permissiemodus" },
|
||||||
|
"config.row.outputStyle": { "translation": "uitvoerstijl" },
|
||||||
|
"config.row.source": { "translation": "bron" },
|
||||||
|
"config.row.source.value": { "translation": "~/.claude + .claude" },
|
||||||
|
"config.footer": { "translation": "vouw een lijst uit om alles te zien · klik op een skill/agent/command → opent de .md" },
|
||||||
|
"config.section.skills": { "translation": "SKILLS" },
|
||||||
|
"config.section.agents": { "translation": "AGENTS" },
|
||||||
|
"config.section.commands": { "translation": "COMMANDS" },
|
||||||
|
"config.section.hooks": { "translation": "HOOKS" },
|
||||||
|
"config.section.permissions": { "translation": "PERMISSIES" },
|
||||||
|
"config.section.mcpServers": { "translation": "MCP SERVERS" },
|
||||||
|
"config.perm.allow": { "translation": "toestaan" },
|
||||||
|
"config.perm.ask": { "translation": "vragen" },
|
||||||
|
"config.perm.deny": { "translation": "weigeren" },
|
||||||
|
"config.control.semantics": { "translation": "{label}: {value}. Klik om te wijzigen." },
|
||||||
|
"config.control.tooltip": { "translation": "{label} wijzigen" },
|
||||||
|
"config.control.option.semantics": { "translation": "{label}: {name}" },
|
||||||
|
"roster.bypass.confirmBody": { "translation": "bypassPermissions inschakelen? Alle tool-aanroepen worden automatisch toegestaan." },
|
||||||
|
"roster.bypass.confirm.semantics": { "translation": "Bypass bevestigen" },
|
||||||
|
"roster.bypass.confirm.tooltip": { "translation": "Bevestigen" },
|
||||||
|
"roster.bypass.ok": { "translation": "OK" },
|
||||||
|
"roster.bypass.cancel.semantics": { "translation": "Bypass annuleren" },
|
||||||
|
"roster.bypass.cancel.tooltip": { "translation": "Annuleren" },
|
||||||
|
"roster.bypass.cancel": { "translation": "Annuleren" },
|
||||||
|
"roster.hidePane": { "translation": "Paneel verbergen" },
|
||||||
|
"roster.showPane": { "translation": "Paneel tonen" },
|
||||||
|
"roster.unmute": { "translation": "Berichten weer aanzetten" },
|
||||||
|
"roster.mute": { "translation": "Berichten dempen" },
|
||||||
|
"roster.inject": { "translation": "Bericht injecteren" },
|
||||||
|
"roster.fork": { "translation": "Sessie forken" },
|
||||||
|
"roster.close": { "translation": "Sessie sluiten" },
|
||||||
|
"roster.inject.cancel": { "translation": "Annuleren" },
|
||||||
|
"taskRow.reassign": { "translation": "Taak opnieuw toewijzen" },
|
||||||
|
"team.empty": { "translation": "Geen team actief." },
|
||||||
|
"team.section.tasks": { "translation": "TAKEN" },
|
||||||
|
"tabStrip.activity": { "translation": "Activiteit" },
|
||||||
|
"tabStrip.team": { "translation": "Team" },
|
||||||
|
"tabStrip.team.count": { "translation": "Team · {count}" },
|
||||||
|
"tabStrip.config": { "translation": "Config" },
|
||||||
|
"teamChat.section.messages": { "translation": "BERICHTEN" },
|
||||||
|
"teamChat.popOut.semantics": { "translation": "Volledig chatpaneel openen" },
|
||||||
|
"teamChat.popOut.tooltip": { "translation": "Volledige chat openen" },
|
||||||
|
"teamChat.empty": { "translation": "Nog geen berichten." },
|
||||||
|
"teamChat.composer.placeholder": { "translation": "@naam of @team …" },
|
||||||
|
"teamChat.pane.title": { "translation": "Teamchat" },
|
||||||
|
"teamChat.interrupt.semantics": { "translation": "Doelsessie onderbreken" },
|
||||||
|
"teamChat.interrupt.label": { "translation": "Onderbreken" },
|
||||||
|
"command.newSecondary": { "translation": "Claude: een secundaire sessie openen" },
|
||||||
|
"command.killAllSessions": { "translation": "Claude: alle sessies voor deze repo afsluiten" },
|
||||||
|
"command.sessionStorage": { "translation": "Claude: sessieopslag (schijfgebruik + opschonen)" },
|
||||||
|
"command.activity.foldLevel": { "translation": "Claude: doorloop het invouwniveau van de activiteit" },
|
||||||
|
"command.agent.show": { "translation": "Claude: een agent-sessiepaneel tonen" },
|
||||||
|
"command.agent.hide": { "translation": "Claude: een agent-sessiepaneel verbergen" },
|
||||||
|
"command.agent.close": { "translation": "Claude: een agent-sessie sluiten (afsluiten)" },
|
||||||
|
"command.agent.mute": { "translation": "Claude: broker-aflevering naar een agent-sessie dempen" },
|
||||||
|
"command.agent.unmute": { "translation": "Claude: broker-aflevering naar een agent-sessie weer aanzetten" },
|
||||||
|
"command.agent.injectMessage": { "translation": "Claude: een tekstbeurt in een agent-sessie injecteren" },
|
||||||
|
"command.agent.setPermissionMode": { "translation": "Claude: permissiemodus voor een agent-sessie instellen" },
|
||||||
|
"command.mode.cycle": { "translation": "Claude: Permissiemodus doorlopen" },
|
||||||
|
"command.task.reassign": { "translation": "Claude: een gedeelde taak opnieuw aan een agent toewijzen" },
|
||||||
|
"command.teamChat.open": { "translation": "Claude: het teamchatpaneel openen" },
|
||||||
|
"command.teamChat.post": { "translation": "Claude: een bericht als gebruiker in het teamkanaal plaatsen" },
|
||||||
|
"command.agent.fork": { "translation": "Claude: een beheerde sessie forken naar een nieuwe branch-sessie" },
|
||||||
|
"settings.activity.title": { "translation": "Activiteit" },
|
||||||
|
"settings.activity.conversation.label": { "translation": "Gesprek" },
|
||||||
|
"settings.activity.foldLevel.label": { "translation": "Invouwniveau" },
|
||||||
|
"settings.activity.foldLevel.help": { "translation": "Hoe agressief het gesprek tool-aanroepen, nadenken en resultaten invouwt." },
|
||||||
|
"settings.activity.opt.none": { "translation": "Alles tonen" },
|
||||||
|
"settings.activity.opt.tools": { "translation": "Tool-aanroepen invouwen" },
|
||||||
|
"settings.activity.opt.thinking": { "translation": "Tools + nadenken invouwen" },
|
||||||
|
"settings.activity.opt.everything": { "translation": "Alles invouwen behalve tekst" },
|
||||||
|
"settings.claude.title": { "translation": "Claude" },
|
||||||
|
"settings.claude.newSessionDefaults.label": { "translation": "Standaardwaarden voor nieuwe sessies" },
|
||||||
|
"settings.claude.model.label": { "translation": "Model" },
|
||||||
|
"settings.claude.model.help": { "translation": "Model voor nieuwe sessies." },
|
||||||
|
"settings.claude.effort.label": { "translation": "Effort" },
|
||||||
|
"settings.claude.effort.help": { "translation": "Reasoning effort voor nieuwe sessies (toegepast via --effort bij het starten)." },
|
||||||
|
"settings.claude.permissionMode.label": { "translation": "Permissiemodus" },
|
||||||
|
"settings.claude.permissionMode.help": { "translation": "Begin-permissiemodus voor nieuwe sessies." }
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"command.clide.installCli": { "translation": "clide: Installeer 'clide'-opdracht in PATH" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Beslissingen" },
|
||||||
|
"tab.detail.title": { "translation": "Beslissing" },
|
||||||
|
"loading": { "translation": "Beslissingen laden..." },
|
||||||
|
"error.load": { "translation": "laden van beslissingen mislukt" },
|
||||||
|
"empty": { "translation": "Geen beslissingen gevonden.\nVoer `pql decisions sync` uit om te indexeren." },
|
||||||
|
"filter.hint": { "translation": "Beslissingen filteren…" },
|
||||||
|
"refresh.tooltip": { "translation": "Beslissingen vernieuwen" },
|
||||||
|
"section.confirmed": { "translation": "BEVESTIGD" },
|
||||||
|
"section.questions": { "translation": "VRAGEN" },
|
||||||
|
"section.rejected": { "translation": "AFGEWEZEN" },
|
||||||
|
"badge.resolved": { "translation": "opgelost" },
|
||||||
|
"detail.loading": { "translation": "Laden…" },
|
||||||
|
"detail.empty": { "translation": "Selecteer een beslissing om de details te bekijken." },
|
||||||
|
"detail.section.refs": { "translation": "KRUISVERWIJZINGEN" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"dialog.title": { "translation": "Een externe link openen?" },
|
||||||
|
"dialog.body": { "translation": "Een clide://-link van buiten de app vraagt om:" },
|
||||||
|
"dialog.warning": { "translation": "Sta dit alleen toe als je vertrouwt waar de link vandaan komt." },
|
||||||
|
"button.cancel": { "translation": "Annuleren" },
|
||||||
|
"button.open": { "translation": "Openen" },
|
||||||
|
"command.deeplink.invoke": { "translation": "Een clide://-deeplink openen" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"command.reset": { "translation": "Indeling: Terugzetten naar Klassiek" },
|
||||||
|
"preset.classic": { "translation": "Klassiek" },
|
||||||
|
"command.palette.toggle": { "translation": "Opdrachtenpalet" },
|
||||||
|
"command.sidebar.collapse": { "translation": "Zijbalk samenvouwen aan/uit" },
|
||||||
|
"command.context.collapse": { "translation": "Contextpaneel samenvouwen aan/uit" },
|
||||||
|
"command.panel.focus.left": { "translation": "Focus op linkerpaneel" },
|
||||||
|
"command.panel.focus.middle": { "translation": "Focus op middelste paneel" },
|
||||||
|
"command.panel.focus.right": { "translation": "Focus op rechterpaneel" },
|
||||||
|
"command.panel.focusMode": { "translation": "Focusmodus aan/uit" },
|
||||||
|
"command.panel.focusMode.exit": { "translation": "Focusmodus afsluiten" },
|
||||||
|
"command.editor.open": { "translation": "Editor openen" },
|
||||||
|
"command.editor.close": { "translation": "Editor sluiten" },
|
||||||
|
"command.workspace.tab.next": { "translation": "Volgend werkruimtetabblad" },
|
||||||
|
"command.workspace.tab.previous": { "translation": "Vorig werkruimtetabblad" },
|
||||||
|
"command.sidebar.section.1": { "translation": "Zijbalk: Sectie 1" },
|
||||||
|
"command.sidebar.section.2": { "translation": "Zijbalk: Sectie 2" },
|
||||||
|
"command.sidebar.section.3": { "translation": "Zijbalk: Sectie 3" },
|
||||||
|
"command.sidebar.section.4": { "translation": "Zijbalk: Sectie 4" },
|
||||||
|
"command.sidebar.section.5": { "translation": "Zijbalk: Sectie 5" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Diff" },
|
||||||
|
"view.semantics": { "translation": "diff-weergave" },
|
||||||
|
"status.loading": { "translation": "Laden…" },
|
||||||
|
"empty.staged": { "translation": "Geen gestagede wijzigingen." },
|
||||||
|
"empty.unstaged": { "translation": "Geen niet-gestagede wijzigingen." },
|
||||||
|
"toolbar.unstaged": { "translation": "Niet gestaged" },
|
||||||
|
"toolbar.unstaged.semantics": { "translation": "niet-gestagede wijzigingen tonen" },
|
||||||
|
"toolbar.staged": { "translation": "Gestaged" },
|
||||||
|
"toolbar.staged.semantics": { "translation": "gestagede wijzigingen tonen" },
|
||||||
|
"meta.newFile": { "translation": "nieuw bestand" },
|
||||||
|
"meta.deleted": { "translation": "verwijderd" },
|
||||||
|
"meta.renamedFrom": { "translation": "hernoemd vanaf {path}" },
|
||||||
|
"meta.binary": { "translation": "binair" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Editor" },
|
||||||
|
"chrome.title": { "translation": "editor" },
|
||||||
|
"empty": { "translation": "Open een bestand om te beginnen met bewerken." },
|
||||||
|
"subtitle.no-buffer": { "translation": "geen buffer · gebruik `clide open <path>` of kies een bestand in de boom" },
|
||||||
|
"a11y.text-area": { "translation": "tekstgebied editor" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"notice.title": { "translation": "Extensiebeheer komt eraan" },
|
||||||
|
"notice.body": { "translation": "Het installeren, in- en uitschakelen van extensies arriveert samen met ondersteuning voor externe (Lua-)extensies. Voorlopig staan de ingebouwde extensies altijd aan." },
|
||||||
|
"notice.tracked": { "translation": "Bijgehouden in T-8 (Tier 6) · D-16" },
|
||||||
|
"settings.extensions.title": { "translation": "Extensies" },
|
||||||
|
"settings.extensions.section.notice": { "translation": "" },
|
||||||
|
"settings.extensions.field.notice.label": { "translation": "" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Bestanden" },
|
||||||
|
"loading": { "translation": "Laden…" },
|
||||||
|
"empty": { "translation": "Geen zichtbare bestanden" },
|
||||||
|
"filter.hint": { "translation": "Bestanden filteren…" },
|
||||||
|
"a11y.tree": { "translation": "bestandsboom — {name}" },
|
||||||
|
"a11y.collapse": { "translation": "{name} samenvouwen" },
|
||||||
|
"a11y.expand": { "translation": "{name} uitvouwen" },
|
||||||
|
"a11y.open": { "translation": "{name} openen" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Git" },
|
||||||
|
"panel.semantics": { "translation": "git-paneel" },
|
||||||
|
"filter.hint": { "translation": "Wijzigingen filteren…" },
|
||||||
|
"status.loading": { "translation": "Laden…" },
|
||||||
|
"status.clean": { "translation": "Niets om vast te leggen, werkmap is schoon." },
|
||||||
|
"group.conflicts": { "translation": "Samenvoegconflicten" },
|
||||||
|
"group.staged": { "translation": "Klaargezet" },
|
||||||
|
"group.changes": { "translation": "Wijzigingen" },
|
||||||
|
"group.untracked": { "translation": "Niet gevolgd" },
|
||||||
|
"action.unstageAll": { "translation": "Alles terugnemen" },
|
||||||
|
"action.stageAll": { "translation": "Alles klaarzetten" },
|
||||||
|
"commit.message.semantics": { "translation": "commitbericht" },
|
||||||
|
"commit.button": { "translation": "Vastleggen" },
|
||||||
|
"commit.button.semantics": { "translation": "klaargezette wijzigingen vastleggen" },
|
||||||
|
"branch.detached": { "translation": "(losgekoppeld)" },
|
||||||
|
"action.pull": { "translation": "Pullen" },
|
||||||
|
"action.pull.semantics": { "translation": "git pull" },
|
||||||
|
"action.push": { "translation": "Pushen" },
|
||||||
|
"action.push.semantics": { "translation": "git push" },
|
||||||
|
"state.added": { "translation": "toegevoegd" },
|
||||||
|
"state.modified": { "translation": "gewijzigd" },
|
||||||
|
"state.deleted": { "translation": "verwijderd" },
|
||||||
|
"state.renamed": { "translation": "hernoemd" },
|
||||||
|
"state.copied": { "translation": "gekopieerd" },
|
||||||
|
"state.untracked": { "translation": "niet gevolgd" },
|
||||||
|
"row.stage.semantics": { "translation": "{name} klaarzetten" },
|
||||||
|
"row.unstage.semantics": { "translation": "{name} terugnemen" },
|
||||||
|
"row.discard.semantics": { "translation": "wijzigingen aan {name} ongedaan maken" },
|
||||||
|
"discard.title": { "translation": "Wijzigingen ongedaan maken?" },
|
||||||
|
"discard.body": { "translation": "Niet-klaargezette wijzigingen aan {name} gaan definitief verloren." },
|
||||||
|
"button.cancel": { "translation": "Annuleren" },
|
||||||
|
"button.discard": { "translation": "Ongedaan maken" },
|
||||||
|
"branch.switch.semantics": { "translation": "branch wisselen — {branch}" },
|
||||||
|
"branchPicker.title": { "translation": "Branch wisselen" },
|
||||||
|
"branchPicker.empty": { "translation": "Geen branches gevonden." },
|
||||||
|
"branchPicker.loadFailed": { "translation": "laden van branches mislukt" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"connected": { "translation": "verbonden" },
|
||||||
|
"connected.hint": { "translation": "backend-isolate is bereikbaar" },
|
||||||
|
"disconnected": { "translation": "niet verbonden" },
|
||||||
|
"disconnected.hint": { "translation": "backend-isolate draait niet" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"command.preset.default": { "translation": "Toetsindeling: Standaard" },
|
||||||
|
"command.preset.vim": { "translation": "Toetsindeling: Vim" },
|
||||||
|
"command.preset.vscode": { "translation": "Toetsindeling: VS Code" },
|
||||||
|
"command.preset.jetbrains": { "translation": "Toetsindeling: JetBrains" },
|
||||||
|
"settings.keymap.title": { "translation": "Toetsindeling" },
|
||||||
|
"settings.keymap.section.preset": { "translation": "Voorinstelling" },
|
||||||
|
"settings.keymap.field.preset.label": { "translation": "Actieve voorinstelling" },
|
||||||
|
"settings.keymap.field.preset.help": { "translation": "Toetsenbordindeling voor de hele app." },
|
||||||
|
"settings.keymap.field.preset.option.default": { "translation": "Standaard" },
|
||||||
|
"settings.keymap.field.preset.option.vim": { "translation": "Vim" },
|
||||||
|
"settings.keymap.field.preset.option.vscode": { "translation": "VS Code" },
|
||||||
|
"settings.keymap.field.preset.option.jetbrains": { "translation": "JetBrains" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"empty": { "translation": "Selecteer een .md-bestand om het hier te bekijken." },
|
||||||
|
"chrome.title": { "translation": "weergave" },
|
||||||
|
"subtitle.lines": { "translation": "{count} regels" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"menu.file": { "translation": "Bestand" },
|
||||||
|
"menu.view": { "translation": "Beeld" },
|
||||||
|
"menu.help": { "translation": "Help" },
|
||||||
|
"about.version": { "translation": "Versie" },
|
||||||
|
"about.commit": { "translation": "Commit" },
|
||||||
|
"about.built": { "translation": "Gebouwd" },
|
||||||
|
"about.repository": { "translation": "Repository" },
|
||||||
|
"licenses.heading": { "translation": "Meegeleverde afhankelijkheden" },
|
||||||
|
"licenses.unavailable": { "translation": "Licenties niet beschikbaar." },
|
||||||
|
"licenses.loading": { "translation": "Laden…" },
|
||||||
|
"button.close": { "translation": "Sluiten" },
|
||||||
|
"button.cancel": { "translation": "Annuleren" },
|
||||||
|
"button.open": { "translation": "Openen" },
|
||||||
|
"button.opening": { "translation": "Openen…" },
|
||||||
|
"button.ok": { "translation": "OK" },
|
||||||
|
"dialog.openProject.title": { "translation": "Project openen" },
|
||||||
|
"dialog.openProject.body": { "translation": "Voer het pad naar een git-repository in." },
|
||||||
|
"dialog.openProject.error": { "translation": "Geen git-repository" },
|
||||||
|
"dialog.notRepo.title": { "translation": "Geen git-repo gevonden" },
|
||||||
|
"dialog.notRepo.body": { "translation": "Een clide-projecthoofdmap vereist een git-repository." },
|
||||||
|
"command.file.openFolder": { "translation": "Bestand: Map openen…" },
|
||||||
|
"command.file.newWindow": { "translation": "Bestand: Nieuw venster" },
|
||||||
|
"command.file.closeWorkspace": { "translation": "Bestand: Project sluiten" },
|
||||||
|
"command.help.about": { "translation": "Help: Over clide" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"a11y.log": { "translation": "uitvoerlogboek" },
|
||||||
|
"empty": { "translation": "Nog geen uitvoer." },
|
||||||
|
"empty.filtered": { "translation": "Geen uitvoer komt overeen met het filter." },
|
||||||
|
"filter.hint": { "translation": "Filteren…" },
|
||||||
|
"chip.level": { "translation": "Niveau: {level}" },
|
||||||
|
"chip.source": { "translation": "Bron: {source}" },
|
||||||
|
"chip.clear": { "translation": "Wissen" },
|
||||||
|
"source.all": { "translation": "alle" },
|
||||||
|
"a11y.jumpToLatest": { "translation": "naar nieuwste springen" },
|
||||||
|
"jump.label": { "translation": "Naar nieuwste ↓" },
|
||||||
|
"a11y.toggleDock": { "translation": "uitvoerdok aan/uit" },
|
||||||
|
"dock.label": { "translation": "Uitvoer" },
|
||||||
|
"command.dock.toggle": { "translation": "Uitvoerdok aan/uit" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "pql" },
|
||||||
|
"tab.links.title": { "translation": "Koppelingen" },
|
||||||
|
"filter.markdown.hint": { "translation": "Markdown filteren…" },
|
||||||
|
"loading": { "translation": "Laden…" },
|
||||||
|
"empty.markdown": { "translation": "Geen markdown-bestanden gevonden." },
|
||||||
|
"search.vault.hint": { "translation": "Vault doorzoeken…" },
|
||||||
|
"search.query.hint": { "translation": "PQL-query…" },
|
||||||
|
"backlinks.empty": { "translation": "Open een bestand om de koppelingen te zien." },
|
||||||
|
"backlinks.loading": { "translation": "Laden…" },
|
||||||
|
"backlinks.semantics": { "translation": "backlinks voor {path}" },
|
||||||
|
"group.backlinks": { "translation": "Backlinks" },
|
||||||
|
"group.outlinks": { "translation": "Uitgaande koppelingen" },
|
||||||
|
"group.label": { "translation": "{label} ({count})" },
|
||||||
|
"group.none": { "translation": "Geen" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Problemen" },
|
||||||
|
"semantics.panel": { "translation": "problemenpaneel" },
|
||||||
|
"filter.hint": { "translation": "Problemen filteren…" },
|
||||||
|
"count": { "translation": "Problemen ({count})" },
|
||||||
|
"refresh.label": { "translation": "Vernieuwen" },
|
||||||
|
"refresh.semantics": { "translation": "problemen vernieuwen" },
|
||||||
|
"scanning": { "translation": "Scannen…" },
|
||||||
|
"empty": { "translation": "Geen problemen gevonden." }
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"mode.find": { "translation": "Zoeken" },
|
||||||
|
"mode.vault": { "translation": "Kluis" },
|
||||||
|
"mode.query": { "translation": "Query" },
|
||||||
|
"mode.markdown": { "translation": "Markdown" },
|
||||||
|
"find.hint": { "translation": "Zoeken" },
|
||||||
|
"replace.hint": { "translation": "Vervangen" },
|
||||||
|
"include.hint": { "translation": "te includeren bestanden (bijv. *.dart)" },
|
||||||
|
"exclude.hint": { "translation": "uit te sluiten bestanden" },
|
||||||
|
"toggle.regex": { "translation": "Reguliere expressie" },
|
||||||
|
"toggle.caseInsensitive": { "translation": "Hoofdletterongevoelig" },
|
||||||
|
"status.searching": { "translation": "Zoeken…" },
|
||||||
|
"status.noResults": { "translation": "Geen resultaten" },
|
||||||
|
"status.counts": { "translation": "{matches} in {files}" },
|
||||||
|
"a11y.openMatch": { "translation": "{path} regel {line} openen" },
|
||||||
|
"button.replaceAll": { "translation": "Alles vervangen" },
|
||||||
|
"button.ok": { "translation": "OK" },
|
||||||
|
"button.cancel": { "translation": "Annuleren" },
|
||||||
|
"button.confirm": { "translation": "Bevestigen" },
|
||||||
|
"dialog.dirty.title": { "translation": "Werkboom niet schoon" },
|
||||||
|
"dialog.dirty.body": { "translation": "Commit of stash je wijzigingen voordat je vervangt — Git is de enige manier om ongedaan te maken." },
|
||||||
|
"dialog.confirm.body": { "translation": "{matches} overeenkomst(en) in {files} bestand(en) vervangen? Dit kan niet ongedaan worden gemaakt in clide." }
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"command.open": { "translation": "Instellingen…" },
|
||||||
|
"modal.title": { "translation": "Instellingen" },
|
||||||
|
"modal.close": { "translation": "Sluiten" },
|
||||||
|
"modal.close.hint": { "translation": "Sluit instellingen zonder iets te wijzigen" },
|
||||||
|
"rail.header": { "translation": "Categorieën" },
|
||||||
|
"panel.empty": { "translation": "Er zijn nog geen instellingscategorieën geregistreerd." },
|
||||||
|
"search.hint": { "translation": "Instellingen zoeken…" },
|
||||||
|
"search.empty": { "translation": "Geen instellingen komen overeen met je zoekopdracht." },
|
||||||
|
"scope.project": { "translation": "Dit project" },
|
||||||
|
"scope.always": { "translation": "Heel clide" },
|
||||||
|
"scope.default": { "translation": "Standaard" },
|
||||||
|
"scope.reset": { "translation": "Terugzetten naar standaard" },
|
||||||
|
"scope.tip.project": { "translation": "Opgeslagen in dit project (.clide)" },
|
||||||
|
"scope.tip.always": { "translation": "Opgeslagen voor heel clide (~/.clide)" },
|
||||||
|
"scope.tip.default": { "translation": "Niet ingesteld — standaard wordt gebruikt" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Terminal" },
|
||||||
|
"chrome.title": { "translation": "terminal" },
|
||||||
|
"subtitle.spawning": { "translation": "shell starten…" },
|
||||||
|
"subtitle.exited": { "translation": "Shell afgesloten." },
|
||||||
|
"error.unavailable": { "translation": "Terminal niet beschikbaar" },
|
||||||
|
"error.daemon": { "translation": "Backend niet verbonden." },
|
||||||
|
"a11y.label": { "translation": "terminal — {subtitle}" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"command.pick": { "translation": "Thema…" },
|
||||||
|
"modal.title": { "translation": "Thema selecteren" },
|
||||||
|
"modal.cancel": { "translation": "Annuleren" },
|
||||||
|
"modal.cancel.hint": { "translation": "Sluit de themakiezer zonder het huidige thema te wijzigen" },
|
||||||
|
"row.select.hint": { "translation": "Dit thema activeren" },
|
||||||
|
"section.appearance": { "translation": "Weergave" },
|
||||||
|
"toggle.highContrast": { "translation": "Hoog contrast" },
|
||||||
|
"settings.appearance.title": { "translation": "Weergave" },
|
||||||
|
"settings.appearance.section.theme": { "translation": "Thema" },
|
||||||
|
"settings.appearance.section.typography": { "translation": "Typografie" },
|
||||||
|
"settings.appearance.field.theme.label": { "translation": "Thema" },
|
||||||
|
"settings.appearance.field.theme.help": { "translation": "Kleurthema; hoog contrast schakelt over naar de toegankelijke variant." },
|
||||||
|
"settings.appearance.field.uiFont.label": { "translation": "UI-lettertype" },
|
||||||
|
"settings.appearance.field.uiFont.help": { "translation": "Lettertype voor de app-interface; wordt direct toegepast." },
|
||||||
|
"settings.appearance.field.uiFont.option.josefinSans": { "translation": "Josefin Sans" },
|
||||||
|
"settings.appearance.field.uiFont.option.inter": { "translation": "Inter" },
|
||||||
|
"settings.appearance.field.monoFont.label": { "translation": "Monospace-lettertype" },
|
||||||
|
"settings.appearance.field.monoFont.help": { "translation": "Terminal, diffs, code en ID's; wordt direct toegepast." },
|
||||||
|
"settings.appearance.field.monoFont.option.jetBrainsMono": { "translation": "JetBrains Mono" },
|
||||||
|
"settings.appearance.field.monoFont.option.firaMono": { "translation": "Fira Mono" },
|
||||||
|
"settings.appearance.section.language": { "translation": "Taal" },
|
||||||
|
"settings.appearance.field.language.label": { "translation": "Taal" },
|
||||||
|
"settings.appearance.field.language.help": { "translation": "Taal voor de app-interface; wordt direct toegepast." }
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"tab.title": { "translation": "Tickets" },
|
||||||
|
"tab.detail.title": { "translation": "Ticket" },
|
||||||
|
"type.initiative": { "translation": "Initiatief" },
|
||||||
|
"type.epic": { "translation": "Epic" },
|
||||||
|
"type.story": { "translation": "Story" },
|
||||||
|
"type.task": { "translation": "Taak" },
|
||||||
|
"type.bug": { "translation": "Bug" },
|
||||||
|
"loading": { "translation": "Tickets laden..." },
|
||||||
|
"error.load": { "translation": "laden van tickets mislukt" },
|
||||||
|
"empty": { "translation": "Geen tickets.\nVoer `pql ticket new` uit om er een aan te maken." },
|
||||||
|
"filter.hint": { "translation": "Tickets filteren…" },
|
||||||
|
"refresh.tooltip": { "translation": "Tickets vernieuwen" },
|
||||||
|
"section.in_progress": { "translation": "IN BEHANDELING" },
|
||||||
|
"section.review": { "translation": "BEOORDELING" },
|
||||||
|
"section.ready": { "translation": "GEREED" },
|
||||||
|
"section.backlog": { "translation": "BACKLOG" },
|
||||||
|
"section.done": { "translation": "KLAAR" },
|
||||||
|
"section.cancelled": { "translation": "GEANNULEERD" },
|
||||||
|
"chip.label": { "translation": "filter op type {type}" },
|
||||||
|
"chip.tooltip": { "translation": "Klik om te wisselen · dubbelklik om te isoleren" },
|
||||||
|
"badge.wip": { "translation": "WIP" },
|
||||||
|
"badge.review": { "translation": "BEOORDELING" },
|
||||||
|
"badge.cancelled": { "translation": "GEANNULEERD" },
|
||||||
|
"pickUp.tooltip": { "translation": "Oppakken — geef dit ticket door aan het Claude-paneel" },
|
||||||
|
"detail.loading": { "translation": "Laden…" },
|
||||||
|
"detail.empty": { "translation": "Selecteer een ticket om de details te bekijken." },
|
||||||
|
"detail.assigned": { "translation": "toegewezen aan: {name}" },
|
||||||
|
"detail.section.parents": { "translation": "BOVENLIGGENDE BOOM" },
|
||||||
|
"detail.section.decisions": { "translation": "GEREFEREERDE BESLISSINGEN" },
|
||||||
|
"status.backlog": { "translation": "BACKLOG" },
|
||||||
|
"status.ready": { "translation": "GEREED" },
|
||||||
|
"status.in_progress": { "translation": "WIP" },
|
||||||
|
"status.review": { "translation": "BEOORDELING" },
|
||||||
|
"status.done": { "translation": "KLAAR" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"command.view.zoomIn": { "translation": "Weergave: Inzoomen" },
|
||||||
|
"command.view.zoomOut": { "translation": "Weergave: Uitzoomen" },
|
||||||
|
"command.view.zoomReset": { "translation": "Weergave: Zoom resetten" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"mode.normal": { "translation": "NORMAL" },
|
||||||
|
"mode.insert": { "translation": "INSERT" },
|
||||||
|
"mode.visual": { "translation": "VISUAL" },
|
||||||
|
"command.vim.mode.normal": { "translation": "Vim: Normale modus" },
|
||||||
|
"command.vim.mode.insert": { "translation": "Vim: Invoegmodus" },
|
||||||
|
"command.vim.mode.visual": { "translation": "Vim: Visuele modus" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"title": { "translation": "clide" },
|
||||||
|
"subtitle": { "translation": "IDE voor Claude Code CLI" },
|
||||||
|
"open-project": { "translation": "Project openen" },
|
||||||
|
"open-project.hint": { "translation": "Kies een git-repository om als werkruimte te openen" },
|
||||||
|
"tab.title": { "translation": "Welkom" },
|
||||||
|
"section.tips": { "translation": "TIPS" },
|
||||||
|
"section.start": { "translation": "STARTEN" },
|
||||||
|
"section.recent": { "translation": "RECENT" },
|
||||||
|
"tips.quickOpen": { "translation": "Snel openen" },
|
||||||
|
"tips.commandPalette": { "translation": "Opdrachtenpalet" },
|
||||||
|
"tips.toggleSidebar": { "translation": "Zijbalk in-/uitschakelen" },
|
||||||
|
"tips.toggleContext": { "translation": "Context in-/uitschakelen" },
|
||||||
|
"tips.findInFiles": { "translation": "Zoeken in bestanden" },
|
||||||
|
"tips.focusMode": { "translation": "Focusmodus" },
|
||||||
|
"action.openFolder": { "translation": "Map openen…" },
|
||||||
|
"recent.empty": { "translation": "Geen recente projecten." },
|
||||||
|
"sticky.label": { "translation": "dit project altijd openen bij opstarten" },
|
||||||
|
"sticky.tooltip": { "translation": "Dit project altijd openen bij opstarten" },
|
||||||
|
"sticky.tooltip.active": { "translation": "Dit project altijd openen bij opstarten (vink uit om de kiezer te herstellen)" },
|
||||||
|
"status.checking": { "translation": "controleren…" },
|
||||||
|
"status.ok": { "translation": "applicatie ok" },
|
||||||
|
"status.notFound": { "translation": "{tool} niet gevonden" },
|
||||||
|
"status.theme": { "translation": "thema: " },
|
||||||
|
"dialog.openProject.title": { "translation": "Project openen" },
|
||||||
|
"dialog.openProject.body": { "translation": "Voer het pad naar een git-repository in." },
|
||||||
|
"dialog.openProject.error": { "translation": "Geen git-repository" },
|
||||||
|
"button.cancel": { "translation": "Annuleren" },
|
||||||
|
"button.open": { "translation": "Openen" },
|
||||||
|
"button.opening": { "translation": "Openen…" },
|
||||||
|
"button.ok": { "translation": "OK" },
|
||||||
|
"dialog.notRepo.title": { "translation": "Geen git-repo gevonden" },
|
||||||
|
"dialog.notRepo.body": { "translation": "Een clide-projecthoofdmap vereist een git-repository." },
|
||||||
|
"command.workspace.open-project": { "translation": "Werkruimte: Project openen…" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"collapser.expand": { "translation": "Uitvouwen" },
|
||||||
|
"collapser.collapse": { "translation": "Samenvouwen" },
|
||||||
|
"collapser.expanded": { "translation": "uitgevouwen" },
|
||||||
|
"collapser.collapsed": { "translation": "samengevouwen" },
|
||||||
|
"toast.dismiss": { "translation": "Melding sluiten" },
|
||||||
|
"lightbox.close": { "translation": "sluiten" },
|
||||||
|
"lightbox.hint": { "translation": "scrollen om te zoomen · dubbelklik om te resetten · Esc om te sluiten" },
|
||||||
|
"tab.new": { "translation": "Nieuw tabblad" },
|
||||||
|
"exline.notCommand": { "translation": "Geen editoropdracht" },
|
||||||
|
"spine.expandSuffix": { "translation": "klik om uit te vouwen" },
|
||||||
|
"pane.close": { "translation": "Paneel sluiten" },
|
||||||
|
"pane.header": { "translation": "paneelkop: {title}" },
|
||||||
|
"reader.back": { "translation": "Terug" },
|
||||||
|
"reader.forward": { "translation": "Vooruit" },
|
||||||
|
"reader.jumpToPin": { "translation": "Naar speld springen" },
|
||||||
|
"reader.edit": { "translation": "Bewerken in editor" },
|
||||||
|
"reader.pin": { "translation": "Vastmaken" },
|
||||||
|
"reader.unpin": { "translation": "Losmaken" },
|
||||||
|
"link.openInEditor": { "translation": "Openen in editor" },
|
||||||
|
"resize.axis.width": { "translation": "breedte" },
|
||||||
|
"resize.axis.height": { "translation": "hoogte" },
|
||||||
|
"resize.sidebar": { "translation": "Zijbalk {axis}" },
|
||||||
|
"resize.contextPanel": { "translation": "Contextpaneel {axis}" },
|
||||||
|
"resize.slot": { "translation": "{slot} {axis}" },
|
||||||
|
"resize.pixels": { "translation": "{n} pixels" }
|
||||||
|
}
|
||||||
@@ -58,8 +58,10 @@ bindings:
|
|||||||
# only claims ctrl+p for selectPrevious `when: palette.open`, so this
|
# only claims ctrl+p for selectPrevious `when: palette.open`, so this
|
||||||
# is conflict-free. Arrow/enter/escape inside the overlay are handled
|
# is conflict-free. Arrow/enter/escape inside the overlay are handled
|
||||||
# locally by the widget.
|
# locally by the widget.
|
||||||
|
# `shift shift` (double-tap) is the JetBrains "Search Everywhere" gesture;
|
||||||
|
# clide aliases it to the quick-open finder across all presets (T-341).
|
||||||
- intent: quickOpen.open
|
- intent: quickOpen.open
|
||||||
keys: [ctrl+p, meta+p]
|
keys: [ctrl+p, meta+p, shift shift]
|
||||||
when: "!palette.open"
|
when: "!palette.open"
|
||||||
# Nav stays on arrows/ctrl+n (not ctrl+p — that's the open chord and
|
# Nav stays on arrows/ctrl+n (not ctrl+p — that's the open chord and
|
||||||
# would collide while the overlay is up).
|
# would collide while the overlay is up).
|
||||||
|
|||||||
@@ -12,11 +12,12 @@
|
|||||||
# IntelliJ's editor-scoped contexts have no producer yet, so global chords
|
# IntelliJ's editor-scoped contexts have no producer yet, so global chords
|
||||||
# stay ungated (they're global in IntelliJ too).
|
# stay ungated (they're global in IntelliJ too).
|
||||||
#
|
#
|
||||||
|
# "Search Everywhere" (double-Shift) maps to clide's quick-open finder via
|
||||||
|
# the `shift shift` double-tap gesture (T-341) — see the quick-open binding.
|
||||||
|
#
|
||||||
# Not bound — no clide command analogue (kept out of scope per the ticket):
|
# Not bound — no clide command analogue (kept out of scope per the ticket):
|
||||||
# Run (Shift+F10), Debug (Shift+F9), Rename/Refactor (Shift+F6), Settings
|
# Run (Shift+F10), Debug (Shift+F9), Rename/Refactor (Shift+F6), Settings
|
||||||
# (Ctrl+Alt+S). And "Search Everywhere" (double-Shift) is not expressible by
|
# (Ctrl+Alt+S).
|
||||||
# the current chord matcher (bare/double modifiers unsupported) — tracked by
|
|
||||||
# T-341; Go to File / Find Action below are the practical stand-ins.
|
|
||||||
|
|
||||||
name: jetbrains
|
name: jetbrains
|
||||||
|
|
||||||
@@ -52,8 +53,10 @@ bindings:
|
|||||||
# -- Quick open: Go to File / Go to Class / Recent Files --------------
|
# -- Quick open: Go to File / Go to Class / Recent Files --------------
|
||||||
# win/linux: Ctrl+Shift+N, Ctrl+N, Ctrl+E. mac: Cmd+Shift+O, Cmd+O,
|
# win/linux: Ctrl+Shift+N, Ctrl+N, Ctrl+E. mac: Cmd+Shift+O, Cmd+O,
|
||||||
# Cmd+E. clide has one fuzzy file finder, so all land on quick-open.
|
# Cmd+E. clide has one fuzzy file finder, so all land on quick-open.
|
||||||
|
# `shift shift` (double-tap) is IntelliJ's "Search Everywhere"; clide
|
||||||
|
# aliases it to the quick-open finder (T-341).
|
||||||
- intent: quickOpen.open
|
- intent: quickOpen.open
|
||||||
keys: [ctrl+shift+n, ctrl+n, ctrl+e, meta+shift+o, meta+o, meta+e]
|
keys: [ctrl+shift+n, ctrl+n, ctrl+e, meta+shift+o, meta+o, meta+e, shift shift]
|
||||||
when: "!palette.open"
|
when: "!palette.open"
|
||||||
- intent: quickOpen.selectNext
|
- intent: quickOpen.selectNext
|
||||||
keys: down
|
keys: down
|
||||||
|
|||||||
+97
-1
@@ -34,8 +34,9 @@ bindings:
|
|||||||
- intent: dismiss
|
- intent: dismiss
|
||||||
keys: escape
|
keys: escape
|
||||||
when: palette.open
|
when: palette.open
|
||||||
|
# `shift shift` (double-tap) aliases to quick-open across presets (T-341).
|
||||||
- intent: quickOpen.open
|
- intent: quickOpen.open
|
||||||
keys: [ctrl+p, meta+p]
|
keys: [ctrl+p, meta+p, shift shift]
|
||||||
when: "!palette.open"
|
when: "!palette.open"
|
||||||
- intent: quickOpen.selectNext
|
- intent: quickOpen.selectNext
|
||||||
keys: [down, ctrl+n]
|
keys: [down, ctrl+n]
|
||||||
@@ -49,6 +50,11 @@ bindings:
|
|||||||
- intent: dismiss
|
- intent: dismiss
|
||||||
keys: escape
|
keys: escape
|
||||||
when: quickOpen.open
|
when: quickOpen.open
|
||||||
|
# Ex command-line overlay (T-407): Esc dismisses it back to normal mode (the
|
||||||
|
# overlay's own EditableText handles Enter via onSubmitted).
|
||||||
|
- intent: dismiss
|
||||||
|
keys: escape
|
||||||
|
when: exline.open
|
||||||
- intent: findInFiles.open
|
- intent: findInFiles.open
|
||||||
keys: [ctrl+shift+f, meta+shift+f]
|
keys: [ctrl+shift+f, meta+shift+f]
|
||||||
- intent: focus.nextPanel
|
- intent: focus.nextPanel
|
||||||
@@ -62,6 +68,96 @@ bindings:
|
|||||||
- intent: text.scaleReset
|
- intent: text.scaleReset
|
||||||
keys: [ctrl+0, meta+0]
|
keys: [ctrl+0, meta+0]
|
||||||
|
|
||||||
|
# ---- Pane navigation (non-editor panes) ------------------------------
|
||||||
|
# When a non-editor pane holds focus (file tree, conversation, lists), the
|
||||||
|
# same motion keys mean NAVIGATION, not buffer edits (T-406). The
|
||||||
|
# `!editor.focused` guard keeps these out of the editor's way; the editor
|
||||||
|
# publishes `editor.focused` while it has focus. These MUST precede the
|
||||||
|
# editor motions below — the resolver takes the first matching binding in
|
||||||
|
# file order, so with a pane focused (editor.focused false) nav wins, and
|
||||||
|
# with the editor focused the `!editor.focused` clause fails and the buffer
|
||||||
|
# motion below wins. Each pane runs its own SequenceMatcher (PaneKeyNav).
|
||||||
|
- intent: nav.down
|
||||||
|
keys: j
|
||||||
|
when: "vim.normal && !editor.focused"
|
||||||
|
- intent: nav.up
|
||||||
|
keys: k
|
||||||
|
when: "vim.normal && !editor.focused"
|
||||||
|
- intent: nav.pageDown
|
||||||
|
keys: ctrl+d
|
||||||
|
when: "vim.normal && !editor.focused"
|
||||||
|
- intent: nav.pageUp
|
||||||
|
keys: ctrl+u
|
||||||
|
when: "vim.normal && !editor.focused"
|
||||||
|
- intent: nav.top
|
||||||
|
keys: "g g" # gg
|
||||||
|
when: "vim.normal && !editor.focused"
|
||||||
|
- intent: nav.bottom
|
||||||
|
keys: shift+g # G
|
||||||
|
when: "vim.normal && !editor.focused"
|
||||||
|
- intent: nav.expandOrRight
|
||||||
|
keys: l
|
||||||
|
when: "vim.normal && !editor.focused"
|
||||||
|
- intent: nav.collapseOrLeft
|
||||||
|
keys: h
|
||||||
|
when: "vim.normal && !editor.focused"
|
||||||
|
- intent: nav.activate
|
||||||
|
keys: [o, enter]
|
||||||
|
when: "vim.normal && !editor.focused"
|
||||||
|
|
||||||
|
# ---- ctrl+w window-command family (T-404) ----------------------------
|
||||||
|
# Multi-chord sequences resolved by the GLOBAL matcher (root_shell), so they
|
||||||
|
# work from any focus. Bare ctrl+w still closes the editor after the ambiguity
|
||||||
|
# timeout (the editor.close binding below / contributions layer). The 3-column
|
||||||
|
# clide layout approximates vim's window grid: h/l focus left/right panels,
|
||||||
|
# j toggles the dock, o is "only" (focus mode), q/c close the editor.
|
||||||
|
- intent: command:panel.focus.left
|
||||||
|
keys: ctrl+w h
|
||||||
|
when: "vim.normal || vim.visual"
|
||||||
|
- intent: command:panel.focus.right
|
||||||
|
keys: ctrl+w l
|
||||||
|
when: "vim.normal || vim.visual"
|
||||||
|
- intent: command:dock.toggle
|
||||||
|
keys: ctrl+w j
|
||||||
|
when: "vim.normal || vim.visual"
|
||||||
|
- intent: focus.nextPanel
|
||||||
|
keys: [ctrl+w w, ctrl+w ctrl+w]
|
||||||
|
when: "vim.normal || vim.visual"
|
||||||
|
- intent: focus.previousPanel
|
||||||
|
keys: ctrl+w shift+w # ctrl+w W
|
||||||
|
when: "vim.normal || vim.visual"
|
||||||
|
- intent: command:panel.focusMode
|
||||||
|
keys: ctrl+w o
|
||||||
|
when: "vim.normal || vim.visual"
|
||||||
|
- intent: command:editor.close
|
||||||
|
keys: [ctrl+w q, ctrl+w c]
|
||||||
|
when: "vim.normal || vim.visual"
|
||||||
|
|
||||||
|
# ---- Tab motions (T-405) ---------------------------------------------
|
||||||
|
# gt / gT cycle the workspace tab strip via the preset-neutral
|
||||||
|
# workspace.tab.* commands (also on ctrl+pagedown/up everywhere). Bare-`g`
|
||||||
|
# sequences are editor/pane-local — the focused editor's matcher or a pane's
|
||||||
|
# PaneKeyNav resolves them and runs the command; they share the `g` prefix
|
||||||
|
# with `g g` (docStart / nav.top), distinguished by the final chord.
|
||||||
|
- intent: command:workspace.tab.next
|
||||||
|
keys: g t # gt
|
||||||
|
when: vim.normal
|
||||||
|
- intent: command:workspace.tab.previous
|
||||||
|
keys: g shift+t # gT
|
||||||
|
when: vim.normal
|
||||||
|
|
||||||
|
# ---- Ex command-line (T-407) -----------------------------------------
|
||||||
|
# `:` opens the transient ex overlay (`:w` `:q` `:wq` `:x` `:e <path>` `:N`);
|
||||||
|
# `ZZ` runs `:wq` directly. Both are typed app intents the editor's command
|
||||||
|
# matcher and a pane's nav matcher bubble to the app-root Actions, so they
|
||||||
|
# fire from any focus. Editor-targeted: no-op when no buffer is active.
|
||||||
|
- intent: exline.open
|
||||||
|
keys: shift+semicolon # :
|
||||||
|
when: vim.normal
|
||||||
|
- intent: exline.writeQuit
|
||||||
|
keys: shift+z shift+z # ZZ
|
||||||
|
when: vim.normal
|
||||||
|
|
||||||
# ---- Mode transitions ------------------------------------------------
|
# ---- Mode transitions ------------------------------------------------
|
||||||
- intent: command:vim.mode.visual
|
- intent: command:vim.mode.visual
|
||||||
keys: v
|
keys: v
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ bindings:
|
|||||||
when: palette.open
|
when: palette.open
|
||||||
|
|
||||||
# -- Quick open / Go to File (Ctrl+P) ---------------------------------
|
# -- Quick open / Go to File (Ctrl+P) ---------------------------------
|
||||||
|
# `shift shift` (double-tap) aliases to quick-open across presets (T-341).
|
||||||
- intent: quickOpen.open
|
- intent: quickOpen.open
|
||||||
keys: [ctrl+p, meta+p]
|
keys: [ctrl+p, meta+p, shift shift]
|
||||||
when: "!palette.open"
|
when: "!palette.open"
|
||||||
- intent: quickOpen.selectNext
|
- intent: quickOpen.selectNext
|
||||||
keys: [down, ctrl+n]
|
keys: [down, ctrl+n]
|
||||||
|
|||||||
+27
-13
@@ -39,7 +39,7 @@ self:
|
|||||||
# Auto-synced from pubspec.yaml `version:` by `make gen-build-info`
|
# Auto-synced from pubspec.yaml `version:` by `make gen-build-info`
|
||||||
# (runs implicitly on every build/run/test). Don't hand-edit; bump
|
# (runs implicitly on every build/run/test). Don't hand-edit; bump
|
||||||
# pubspec instead.
|
# pubspec instead.
|
||||||
version: "2.3.3"
|
version: "2.8.0"
|
||||||
homepage: https://github.com/postmeridiem/clide
|
homepage: https://github.com/postmeridiem/clide
|
||||||
license: MIT
|
license: MIT
|
||||||
license_file: assets/LICENSE
|
license_file: assets/LICENSE
|
||||||
@@ -54,10 +54,32 @@ dependencies:
|
|||||||
license: OFL-1.1
|
license: OFL-1.1
|
||||||
license_file: assets/fonts/jetbrains_mono/OFL.txt
|
license_file: assets/fonts/jetbrains_mono/OFL.txt
|
||||||
purpose: >-
|
purpose: >-
|
||||||
Monospace face for terminal panes, diff views, code editors, and
|
Default monospace face for terminal panes, diff views, code editors, and
|
||||||
any other monospace surface.
|
any other monospace surface.
|
||||||
weights_bundled: [Regular, Italic, Bold, BoldItalic]
|
weights_bundled: [Regular, Italic, Bold, BoldItalic]
|
||||||
|
|
||||||
|
- name: Fira Mono
|
||||||
|
kind: font
|
||||||
|
version: "3.206"
|
||||||
|
homepage: https://github.com/mozilla/Fira
|
||||||
|
license: OFL-1.1
|
||||||
|
license_file: assets/fonts/fira_mono/OFL.txt
|
||||||
|
purpose: >-
|
||||||
|
Selectable monospace face (Settings → Appearance, T-471); JetBrains Mono
|
||||||
|
remains the default.
|
||||||
|
weights_bundled: [Regular, Bold]
|
||||||
|
|
||||||
|
- name: Inter
|
||||||
|
kind: font
|
||||||
|
version: "variable"
|
||||||
|
homepage: https://github.com/rsms/inter
|
||||||
|
license: OFL-1.1
|
||||||
|
license_file: assets/fonts/inter/OFL.txt
|
||||||
|
purpose: >-
|
||||||
|
Selectable application UI face (bundled T-460); Josefin Sans is the
|
||||||
|
default. Variable font with optical-size and weight axes.
|
||||||
|
weights_bundled: [VariableFont, Italic-VariableFont]
|
||||||
|
|
||||||
- name: Josefin Sans
|
- name: Josefin Sans
|
||||||
kind: font
|
kind: font
|
||||||
version: "variable"
|
version: "variable"
|
||||||
@@ -65,8 +87,9 @@ dependencies:
|
|||||||
license: OFL-1.1
|
license: OFL-1.1
|
||||||
license_file: assets/fonts/josefin_sans/OFL.txt
|
license_file: assets/fonts/josefin_sans/OFL.txt
|
||||||
purpose: >-
|
purpose: >-
|
||||||
Application UI face. Default weight Light (300); full 100-700
|
Default application UI face; default weight Light (300), full 100-700
|
||||||
range available via the variable-font weight axis.
|
range via the variable-font weight axis. Inter is bundled as a
|
||||||
|
selectable alternative.
|
||||||
weights_bundled: [VariableFont, Italic-VariableFont]
|
weights_bundled: [VariableFont, Italic-VariableFont]
|
||||||
|
|
||||||
- name: Phosphor Icons
|
- name: Phosphor Icons
|
||||||
@@ -205,15 +228,6 @@ dependencies:
|
|||||||
# Build-time-only dependencies — test runners, mocks, lints. Tracked
|
# Build-time-only dependencies — test runners, mocks, lints. Tracked
|
||||||
# here for audit completeness; NOT rendered in the About screen.
|
# here for audit completeness; NOT rendered in the About screen.
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
- name: mocktail
|
|
||||||
kind: dart-package
|
|
||||||
version: "1.0.5"
|
|
||||||
homepage: https://pub.dev/packages/mocktail
|
|
||||||
license: MIT
|
|
||||||
purpose: >-
|
|
||||||
Mocks at IO / IPC boundaries. ChangeNotifier facades use
|
|
||||||
hand-rolled fakes instead of mocks (D-025).
|
|
||||||
|
|
||||||
- name: alchemist
|
- name: alchemist
|
||||||
kind: dart-package
|
kind: dart-package
|
||||||
version: "0.12.1"
|
version: "0.12.1"
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Build the C `clide` client with MSVC on Windows (Git Bash / MSYS).
|
||||||
|
# Wrapped by `make clide-cli` — don't run directly (see CLAUDE.md
|
||||||
|
# tooling discipline). Finds the VC++ toolset via vswhere, loads the
|
||||||
|
# x64 dev environment, compiles:
|
||||||
|
# native/clide-cli/clide.c -> native/windows-x64/clide.exe
|
||||||
|
# ws2_32.lib supplies winsock (AF_UNIX socket support).
|
||||||
|
set -e
|
||||||
|
|
||||||
|
VSWHERE="/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe"
|
||||||
|
if [ ! -x "$VSWHERE" ]; then
|
||||||
|
echo "vswhere.exe not found — install Visual Studio (Build Tools) with the C++ workload" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
VSROOT=$("$VSWHERE" -products '*' -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath | tr -d '\r')
|
||||||
|
if [ -z "$VSROOT" ]; then
|
||||||
|
echo "no Visual Studio C++ x64 toolset found (vswhere returned nothing)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p native/windows-x64
|
||||||
|
# A generated .bat sidesteps the unwinnable sh->cmd quote escaping for
|
||||||
|
# the space-laden VS path. //c keeps MSYS from path-mangling cmd's /c
|
||||||
|
# switch; /Fo drops the .obj next to the .exe so the repo root stays
|
||||||
|
# clean.
|
||||||
|
BAT=$(mktemp --suffix=.bat)
|
||||||
|
trap 'rm -f "$BAT"' EXIT
|
||||||
|
cat > "$BAT" <<EOF
|
||||||
|
@call "$VSROOT\\Common7\\Tools\\VsDevCmd.bat" -arch=amd64 -no_logo
|
||||||
|
@cl /nologo /O2 /W4 /D_CRT_SECURE_NO_WARNINGS native\\clide-cli\\clide.c /Fonative\\windows-x64\\ /Fe:native\\windows-x64\\clide.exe ws2_32.lib
|
||||||
|
EOF
|
||||||
|
cmd.exe //c "$(cygpath -w "$BAT")"
|
||||||
|
rm -f native/windows-x64/clide.obj
|
||||||
+6
-1
@@ -35,7 +35,12 @@ echo "==> dart test (pty — unreliable under the flutter test runner; serial)"
|
|||||||
# --concurrency=1: these spawn real PTYs and compete for fds when run in
|
# --concurrency=1: these spawn real PTYs and compete for fds when run in
|
||||||
# parallel, which flaked them (registry/session). Serialize — the proper fix
|
# parallel, which flaked them (registry/session). Serialize — the proper fix
|
||||||
# for resource-bound tests, vs. the old per-test `retry:` band-aid. (T-193)
|
# for resource-bound tests, vs. the old per-test `retry:` band-aid. (T-193)
|
||||||
dart test -r "$REPORTER" --concurrency=1 --tags pty test/pty/session_test.dart test/panes/registry_test.dart
|
# windows_pty_test is the ConPTY sibling of session_test; each suite
|
||||||
|
# self-skips off-platform, so the union always contributes tests.
|
||||||
|
# --timeout 60s matches the flutter lines below: a wedged PTY test (e.g. a
|
||||||
|
# ConPTY reader blocked forever in ReadFile) fails fast instead of hanging the
|
||||||
|
# whole serial run.
|
||||||
|
dart test -r "$REPORTER" --concurrency=1 --timeout 60s --tags pty test/pty/session_test.dart test/panes/registry_test.dart test/pty/windows_pty_test.dart
|
||||||
|
|
||||||
# The parallel pool excludes both pty (runs under dart test, above) and
|
# The parallel pool excludes both pty (runs under dart test, above) and
|
||||||
# serial-tagged tests (concurrency-vulnerable — run in their own --concurrency=1
|
# serial-tagged tests (concurrency-vulnerable — run in their own --concurrency=1
|
||||||
|
|||||||
@@ -3,10 +3,16 @@
|
|||||||
# start" regression gate. Flutter integration tests prefer one file at
|
# start" regression gate. Flutter integration tests prefer one file at
|
||||||
# a time on desktop; we iterate to avoid the "Unable to start the app"
|
# a time on desktop; we iterate to avoid the "Unable to start the app"
|
||||||
# error that hits when they run as a batch.
|
# error that hits when they run as a batch.
|
||||||
|
#
|
||||||
|
# -d linux pins the desktop device explicitly: the GitHub ubuntu-latest
|
||||||
|
# runner exposes BOTH a linux desktop AND a chrome web device, so a bare
|
||||||
|
# `flutter test integration_test/...` aborts with "More than one device
|
||||||
|
# connected" before it ever compiles (the dev box / old Gitea runner only
|
||||||
|
# had the one device, so this was latent until CI moved to GitHub).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
for f in integration_test/*_test.dart; do
|
for f in integration_test/*_test.dart; do
|
||||||
echo "==> integration_test: $f"
|
echo "==> integration_test: $f"
|
||||||
flutter test "$f"
|
flutter test -d linux "$f"
|
||||||
done
|
done
|
||||||
|
|||||||
-776
@@ -1,776 +0,0 @@
|
|||||||
# clide — External Consultant Review
|
|
||||||
|
|
||||||
**Date:** 2026-05-14
|
|
||||||
**Scope:** Full-repository assessment of clide at `main` (commit `9030e56`).
|
|
||||||
**Method:** Six independent specialist reviewers, each given read-only access and a
|
|
||||||
brief covering best practice, clean code, architecture, usability, stability,
|
|
||||||
expandability, style, consistency, and general quality. Reviewers did not see each
|
|
||||||
other's findings; cross-cutting themes below are genuine independent agreement.
|
|
||||||
|
|
||||||
**Panel:**
|
|
||||||
| Lens | Reviewer |
|
|
||||||
|---|---|
|
|
||||||
| Architecture | Software Architect |
|
|
||||||
| Tests & quality gates | Test / QA Analyst |
|
|
||||||
| UX & accessibility | UX & Accessibility Expert |
|
|
||||||
| Code quality & craft | Senior Dart/Flutter Engineer |
|
|
||||||
| Security & supply chain | Security Engineer |
|
|
||||||
| Docs, governance & DX | TPM / Developer-Experience Consultant |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overall verdict
|
|
||||||
|
|
||||||
clide is, for a solo-dev pre-v2.0 project, **unusually disciplined** — every reviewer
|
|
||||||
said so independently. The governance system is alive, the core subsystems are small
|
|
||||||
and well-typed, the FFI/PTY layer shows real systems-programming care, and the quality
|
|
||||||
gates are genuine rather than ornamental. The codebase is in good shape.
|
|
||||||
|
|
||||||
The weaknesses cluster into a handful of themes, and several are **load-bearing**: a
|
|
||||||
central guardrail (CLI-first IPC) has no runtime implementation, keyboard operability —
|
|
||||||
the core requirement of a power-user dev tool — is largely unbuilt, an untrusted
|
|
||||||
workspace can achieve code execution, and the two primary onboarding documents describe
|
|
||||||
an architecture that no longer exists.
|
|
||||||
|
|
||||||
None of these are fatal; all are fixable; most have quick-win first steps. But they
|
|
||||||
should be addressed before a public v2.0.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Cross-cutting themes (independent agreement)
|
|
||||||
|
|
||||||
These were each flagged by **two or more** reviewers who did not coordinate:
|
|
||||||
|
|
||||||
1. **The IPC layer is mid-migration and contradicts itself.** The Architect found no
|
|
||||||
Unix-socket *server* anywhere in `lib/` — D-56's "app hosts an in-process IPC server"
|
|
||||||
and D-1's "CLI-first, not MCP" have no runtime path; three IPC clients
|
|
||||||
(`DaemonClient`, `InProcessClient`, `IsolateClient`) coexist with two duplicated
|
|
||||||
service-wiring sites. The Security reviewer independently noted `DaemonClient`'s
|
|
||||||
socket code is still live as an unvalidated attack surface. **Pick one IPC model,
|
|
||||||
implement or amend D-56, delete the other two.**
|
|
||||||
|
|
||||||
2. **The "no pre-existing excuse / clean board" guardrail is being violated right now.**
|
|
||||||
`flutter analyze` reports 9 `unnecessary_import` issues in `test/`; `ci/test.sh` runs
|
|
||||||
analyze with `--no-fatal-infos`, which silently tolerates them. Flagged by the
|
|
||||||
Architect, Code Quality, and Test reviewers. The repo's own rules say fix-first.
|
|
||||||
|
|
||||||
3. **`lib/src/terminal/` is in an undeclared middle state.** ~7k LOC forked from
|
|
||||||
xterm.dart, carrying commented-out `print`s, dangling TODOs, a 1137-line `parser.dart`,
|
|
||||||
and the only `invalid_use_of_protected_member` suppression in the repo. MEMORY says
|
|
||||||
"code under `lib/` is owned, not vendored" — so it must either be formally vendored
|
|
||||||
(frozen, documented, decision-recorded) or cleaned to the project bar. Flagged by
|
|
||||||
Code Quality; the Architect's "consistency" deduction points at the same seam.
|
|
||||||
|
|
||||||
4. **Documentation describes a dissolved architecture.** `README.md` and
|
|
||||||
`docs/initial-plan.md` still describe a Go sidecar, `ptyc/` C helper, `app/`
|
|
||||||
subdirectory, and a separate `clide --daemon` process — all removed by D-5, D-56, and
|
|
||||||
the FFI pivot. A new contributor's first read builds a wrong mental model.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Consolidated scorecard
|
|
||||||
|
|
||||||
Scores are each reviewer's, 1–5, on their own dimensions.
|
|
||||||
|
|
||||||
| Domain | Dimension | Score |
|
|
||||||
|---|---|---|
|
|
||||||
| **Architecture** | Layering & dependency direction | 4 |
|
|
||||||
| | Separation of concerns | 4 |
|
|
||||||
| | Expandability | 5 |
|
|
||||||
| | Consistency | 4 |
|
|
||||||
| | Guardrail adherence | 3 |
|
|
||||||
| **Tests** | Coverage quality | 4 |
|
|
||||||
| | Test reliability / flakiness | 3 |
|
|
||||||
| | Gate trustworthiness | 3 |
|
|
||||||
| | Test maintainability | 5 |
|
|
||||||
| | Regression-catching power | 4 |
|
|
||||||
| **UX / a11y** | Interaction model | 2 |
|
|
||||||
| | Accessibility | 3 |
|
|
||||||
| | Visual consistency | 4 |
|
|
||||||
| | Discoverability | 2 |
|
|
||||||
| | State coverage (loading/error/empty) | 3 |
|
|
||||||
| **Code quality** | Idiomatic Dart | 4 |
|
|
||||||
| | Error handling | 4 |
|
|
||||||
| | Naming & readability | 4 |
|
|
||||||
| | Consistency across subsystems | 3 |
|
|
||||||
| | Resource / lifecycle safety | 4 |
|
|
||||||
| **Security** | Subprocess safety | 2 |
|
|
||||||
| | IPC input validation | 3 |
|
|
||||||
| | Path / filesystem safety | 3 |
|
|
||||||
| | Dependency / supply-chain hygiene | 3 |
|
|
||||||
| | Secrets & sandboxing | 3 |
|
|
||||||
| **Docs / governance** | Governance discipline | 4 |
|
|
||||||
| | Documentation accuracy | 2 |
|
|
||||||
| | Changelog hygiene | 3 |
|
|
||||||
| | Contributor onboarding | 2 |
|
|
||||||
| | Convention adherence | 4 |
|
|
||||||
|
|
||||||
**Highest marks:** expandability (5), test maintainability (5). The extension contract
|
|
||||||
and test-helper design are genuine standouts.
|
|
||||||
**Lowest marks:** interaction model (2), discoverability (2), subprocess safety (2),
|
|
||||||
documentation accuracy (2), contributor onboarding (2).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Prioritized action list
|
|
||||||
|
|
||||||
Synthesized across all six reviews. Severity is the highest any reviewer assigned.
|
|
||||||
|
|
||||||
### Critical — address before public v2.0
|
|
||||||
|
|
||||||
1. **Fix untrusted-workspace code execution.** `toolchain_paths.dart:79` resolves
|
|
||||||
`native/dugite/bin/git` relative to the *workspace root*; a malicious repo can plant
|
|
||||||
an executable there that clide runs on the first auto-fired `git.status`. Resolve
|
|
||||||
`native/dugite` against `Platform.resolvedExecutable`'s directory, never the
|
|
||||||
workspace. *(Security)*
|
|
||||||
2. **Resolve the IPC story.** Either implement the in-process Unix-socket server per
|
|
||||||
D-56 so the `clide` CLI / C client actually works, or amend D-56 to make in-process
|
|
||||||
direct dispatch the design and delete `DaemonClient`'s socket code, `IsolateClient`,
|
|
||||||
`Backend`, and `backend_entry.dart`. Today the code claims three models and runs one,
|
|
||||||
and a load-bearing guardrail (D-1/D-6) is unmet. *(Architecture, Security)*
|
|
||||||
3. **Make the tool keyboard-operable.** `ClideTappable` (base of nearly every
|
|
||||||
interactive widget) is mouse-only — no `Focus`, no Enter/Space. The command palette
|
|
||||||
has no arrow-key navigation and no Escape. For a keyboard-first dev tool this is a
|
|
||||||
functional gap, not a polish item. *(UX)*
|
|
||||||
4. **Fix the onboarding docs.** Rewrite `README.md`'s `ptyc/` / `make ptyc-build`
|
|
||||||
sections, fix its dead `decisions/` link, and banner `docs/initial-plan.md` as
|
|
||||||
historical (or split out a current `docs/architecture.md`). *(Docs)*
|
|
||||||
|
|
||||||
### Major — should land soon
|
|
||||||
|
|
||||||
5. Add symlink re-resolution + containment re-check in `files.read` / `files.ls` — a
|
|
||||||
repo symlink `config -> /etc/shadow` currently passes path-safety. *(Security)*
|
|
||||||
6. Add `test-integration` (and ideally `smoke-bundle`) to `make push-check` — the gate
|
|
||||||
that catches "app won't boot" is currently omitted from the pre-push gate. *(Tests)*
|
|
||||||
7. Schema-validate the IPC argument surface; reject `-`-prefixed `branch`/`remote`/`path`
|
|
||||||
values; add size/count bounds. *(Security)*
|
|
||||||
8. Establish a real focus-traversal model (`FocusTraversalGroup` per slot, a documented
|
|
||||||
"focus next panel" keybinding) and integrate `FocusTracker` with Flutter's focus
|
|
||||||
system instead of paralleling it. *(UX)*
|
|
||||||
9. Fix the `SchedulerService._startTicker` isolate-spawn race — a `_stopTicker()` before
|
|
||||||
the spawn future resolves leaks a forever-ticking isolate. Mirror `NativePty`'s
|
|
||||||
`_readerReady` pattern. *(Code quality)*
|
|
||||||
10. Decide the status of `lib/src/terminal/` — formally vendor (and decision-record) it,
|
|
||||||
or do the cleanup sweep. *(Code quality)*
|
|
||||||
11. Replace fixed wall-clock `Future.delayed` sleeps in `watcher_test.dart` /
|
|
||||||
`session_test.dart` with event-driven waits; make swallowed `onTimeout` callbacks
|
|
||||||
`fail()` loudly. *(Tests)*
|
|
||||||
12. Write a human-facing `CONTRIBUTING.md`; cut an interim release to drain the ~80-commit
|
|
||||||
`[Unreleased]` backlog; merge duplicate changelog subsection headings. *(Docs)*
|
|
||||||
13. Single global `KeyboardListener` → scoped `Shortcuts`/`Actions`; move
|
|
||||||
`KeybindingResolver` off layout-dependent `keyLabel`. *(UX)*
|
|
||||||
|
|
||||||
### Quick wins — hours each
|
|
||||||
|
|
||||||
- Clear the 9 `unnecessary_import` analyzer issues; drop `--no-fatal-infos` from
|
|
||||||
`ci/test.sh`. *(Architecture, Tests, Code quality)*
|
|
||||||
- Run the `forkpty` PTY tests with `--coverage` so `native_pty.dart` — the riskiest file
|
|
||||||
— is honestly measured. *(Tests)*
|
|
||||||
- Add a `Focus` + Enter/Space wrapper and a focus-ring inside `ClideTappable`; this fixes
|
|
||||||
the keyboard gap for every button and list item at once. *(UX)*
|
|
||||||
- Add arrow-key + Escape + selected-index to `ClidePalette` (copy the existing
|
|
||||||
`_ProjectSwitcherDropdown` `onKeyEvent` pattern). *(UX)*
|
|
||||||
- Amend D-66 to reflect the coverage floor's real location (`pubspec.yaml`), mechanism,
|
|
||||||
and value (90%) — it currently disagrees with the changelog and the code. *(Docs)*
|
|
||||||
- Reconcile `licenses.yaml` with `pubspec.yaml` (`test` version drift, phantom `lints`
|
|
||||||
entry); add a `native/SHA256SUMS` manifest. *(Security, Docs)*
|
|
||||||
- Replace silent `catch (_)` in `tree_sitter_ffi.dart` with a logged last-error.
|
|
||||||
*(Code quality)*
|
|
||||||
- Fix the `clide.dart` barrel leak in `file_tree_view.dart:8`; narrow the barrel (drop
|
|
||||||
the `dispatcher.dart` export); move `test_app.dart` out of the production `main.dart`
|
|
||||||
import graph. *(Architecture)*
|
|
||||||
- Expand the contrast gate's `canonicalPairs` to cover `globalTextMuted`, the `status*`
|
|
||||||
colors, and `panelActiveBorder`. *(UX)*
|
|
||||||
- Triage stale governance Q-records (Q-1/2/3/25 overtaken by shipped Tier-1 work).
|
|
||||||
*(Docs)*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Full reviews
|
|
||||||
|
|
||||||
## 1. Architecture — Software Architect
|
|
||||||
|
|
||||||
### Executive summary
|
|
||||||
|
|
||||||
clide is an unusually disciplined solo-dev codebase. The governance system (67
|
|
||||||
D-records, tracked Q/R) is real and largely honored in code, the kernel/extension split
|
|
||||||
is coherent, and the feature-first layout with barrel files is consistently applied. The
|
|
||||||
single biggest strength is the **extension contract**: every built-in — including layout
|
|
||||||
itself — passes the same `ClideExtension` + `ContributionPoint` contract, which is the
|
|
||||||
best possible proof the contract is usable. The single biggest risk is **architectural
|
|
||||||
drift in the IPC layer**: D-56 mandates the Flutter app host an in-process IPC server
|
|
||||||
reachable by a thin C client over a unix socket, but no socket server exists anywhere in
|
|
||||||
`lib/` — the "CLI-first, not MCP" guardrail (D-1) has no runtime path today. Compounding
|
|
||||||
this, three parallel IPC client implementations (`DaemonClient` socket,
|
|
||||||
`InProcessClient`, `IsolateClient` + `Backend`) coexist with two competing
|
|
||||||
service-wiring sites (`main.dart` and `backend_entry.dart`), suggesting an unfinished
|
|
||||||
migration.
|
|
||||||
|
|
||||||
### Strengths
|
|
||||||
|
|
||||||
- **Extension contract is clean and scales** — `lib/extension/src/extension.dart` +
|
|
||||||
`contribution.dart`: sealed `ContributionPoint` hierarchy, `ClideExtensionContext`
|
|
||||||
lists services explicitly (deliberately avoiding a `KernelServices` import cycle —
|
|
||||||
`extension.dart:50-52`). `ExtensionManager` does dependency topo-sort,
|
|
||||||
dependency-gated activation, and contribution apply/remove symmetrically
|
|
||||||
(`extensions_manager.dart:164-202`). Adding a pane = new extension file + one
|
|
||||||
`register()` line in `main.dart`.
|
|
||||||
- **Kernel admission rule is enforced, not aspirational** — D-12's "mandatory shared
|
|
||||||
singleton" test visibly shaped `KernelServices` (`facade.dart:38-93`); ~25 services,
|
|
||||||
each defensibly cross-cutting. The two-tier disable model (D-14) is honored:
|
|
||||||
`default_layout` is itself an extension.
|
|
||||||
- **Feature-first layout with barrel discipline** (D-8) is consistent — every
|
|
||||||
`builtin/<name>/` and `kernel/` has a barrel; builtins import
|
|
||||||
`package:clide/kernel/kernel.dart`, not deep paths. Only one leak found.
|
|
||||||
- **Governance-to-code traceability is genuine** — `WidgetsApp` root (D-7) at
|
|
||||||
`app.dart:38`, `ChangeNotifier`/`ListenableBuilder` state (D-10) everywhere, git
|
|
||||||
hardcoded in toolchain/project loader (D-13), terminal correctly tagged
|
|
||||||
`inlined-source` in `licenses.yaml` with modifications documented.
|
|
||||||
- **Git subsystem cohesion** — `lib/src/git/` cleanly split into `client` / `status` /
|
|
||||||
`diff` / `operations` (~250 lines each), each a single responsibility.
|
|
||||||
|
|
||||||
### Findings
|
|
||||||
|
|
||||||
- **[Critical] No IPC socket server exists** — D-56 specifies the app hosts an
|
|
||||||
in-process IPC server with a C client connecting over a unix socket. `grep` for
|
|
||||||
`ServerSocket`/unix-domain `bind` in `lib/` returns nothing. `DaemonClient._connect`
|
|
||||||
(`client.dart:72-94`) *connects* to a socket, but nothing *serves* one. Today the only
|
|
||||||
working path is `InProcessClient` (`in_process.dart`), which calls the dispatcher
|
|
||||||
directly in-process. **Claude cannot drive clide via `clide ...` — the CLI-first
|
|
||||||
guardrail (D-1, D-6) has no implementation.** This is the load-bearing contract of the
|
|
||||||
whole project and it is absent.
|
|
||||||
- **[Major] Three IPC clients + two wiring sites = unfinished migration** —
|
|
||||||
`DaemonClient` (socket), `InProcessClient`, and `IsolateClient`+`Backend`/
|
|
||||||
`backend_entry.dart` all coexist. `main.dart:76-113` wires subsystems via
|
|
||||||
`buildDispatcher`; `backend_entry.dart:40-110` wires the *same* five subsystems again
|
|
||||||
inside an isolate. `Backend.spawn` is referenced only by `facade.dart` but `main.dart`
|
|
||||||
uses `autoStartDaemonClient: false` + `daemonClientFactory` (the in-process path).
|
|
||||||
Dead-or-dormant isolate infrastructure with duplicated registration logic — pick one
|
|
||||||
and delete the others.
|
|
||||||
- **[Major] `main.dart` (production entry) imports `test_app.dart`** — `main.dart:2` and
|
|
||||||
`:51-55`. The production binary carries the test harness and branches on
|
|
||||||
`CLIDE_TESTMODE`. Test scaffolding should not be reachable from the shipping entry
|
|
||||||
point; gate it behind a separate entrypoint or `kDebugMode`.
|
|
||||||
- **[Minor] `flutter analyze` reports 9 issues** — all `unnecessary_import` in `test/`,
|
|
||||||
but CLAUDE.md's "no pre-existing excuse" / "clean board" guardrail makes this a
|
|
||||||
fix-first item.
|
|
||||||
- **[Minor] Barrel leak** — `lib/builtin/files/src/file_tree_view.dart:8` imports
|
|
||||||
`package:clide/src/files/listing.dart` directly instead of via
|
|
||||||
`package:clide/clide.dart` (which already re-exports `FileEntry`).
|
|
||||||
- **[Minor] `clide.dart` barrel exports the daemon dispatcher** — `clide.dart:15`
|
|
||||||
exports `src/daemon/dispatcher.dart`. The barrel is described as "shared types"; the
|
|
||||||
dispatcher is server-side machinery.
|
|
||||||
- **[Minor] `ExtensionManager.activate` swallows exceptions** (`extensions_manager.dart:
|
|
||||||
141-143`) — a failed `activate()` logs and continues, leaving the extension
|
|
||||||
un-activated but `_known`, with no surfaced "degraded" state for the UI.
|
|
||||||
|
|
||||||
### Recommendations
|
|
||||||
|
|
||||||
**Quick wins:** clear the 9 analyzer issues; fix the `file_tree_view.dart` barrel leak
|
|
||||||
(consider a CI grep gate for `package:clide/src/` imports outside their feature); move
|
|
||||||
`test_app.dart` out of the production import graph; drop the `dispatcher.dart` export
|
|
||||||
from `clide.dart`.
|
|
||||||
|
|
||||||
**Larger efforts:** resolve the IPC story (implement the socket server per D-56, or
|
|
||||||
amend D-56 and delete `DaemonClient`/`IsolateClient`/`Backend`/`backend_entry.dart`);
|
|
||||||
collapse subsystem wiring into one `registerAllSubsystems(...)` function; give
|
|
||||||
`ExtensionManager` a surfaced failure state so the UI can show degraded built-ins.
|
|
||||||
|
|
||||||
### Scorecard
|
|
||||||
|
|
||||||
| Dimension | Score | Justification |
|
|
||||||
|---|---|---|
|
|
||||||
| Layering & dependency direction | 4/5 | Kernel→extension direction clean, context-vs-aggregate split avoids cycles; docked for the `src/`↔`kernel/src/` barrel leak and the dispatcher export. |
|
|
||||||
| Separation of concerns | 4/5 | Feature-first layout, single-responsibility subsystems; duplicated subsystem registration is the blemish. |
|
|
||||||
| Expandability | 5/5 | New pane = one extension file + one `register()` line; sealed contribution hierarchy; layout itself is data and extension-shaped. |
|
|
||||||
| Consistency | 4/5 | Barrels, naming, D-record back-references uniform; three coexisting IPC clients and 9 analyzer issues break the bar. |
|
|
||||||
| Guardrail adherence | 3/5 | `WidgetsApp`, single-process, no-Material, governance, zero-deps all honored — but D-1/D-6/D-56 (CLI-first via socket server) have no runtime implementation. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Tests & quality gates — Test / QA Analyst
|
|
||||||
|
|
||||||
### Executive summary
|
|
||||||
|
|
||||||
The clide test suite is, for a solo-dev pre-2.0 project, in genuinely good shape. ~104
|
|
||||||
test files against 276 lib files, ~92.75% line coverage, and — critically — the coverage
|
|
||||||
was *not* bought with assertion-free filler. Even the alarmingly-named files
|
|
||||||
(`coverage_trivials_test.dart`, `zero_coverage_widgets_test.dart`,
|
|
||||||
`services_stubs_test.dart`, `mop_up_test.dart`) contain real behavioral assertions. The
|
|
||||||
biggest strength is a sensibly layered pyramid with a real boot-path integration gate
|
|
||||||
and a startup smoke test that catches the "tests pass but app won't launch" class. The
|
|
||||||
biggest risk is **flakiness from wall-clock-dependent tests** — fixed `Future.delayed`
|
|
||||||
sleeps in file-watcher and PTY tests will eventually produce intermittent CI failures,
|
|
||||||
and the PTY tests are run via `dart test` so they are **excluded from the coverage
|
|
||||||
measurement entirely**.
|
|
||||||
|
|
||||||
### Strengths
|
|
||||||
|
|
||||||
- **Test pyramid is sound.** Pure-Dart unit, widget tests with a shared harness, golden
|
|
||||||
tests (Alchemist), an a11y contract layer, and 3 real-boot `integration_test/` files —
|
|
||||||
correctly separated by runner (`ci/test.sh` vs `ci/test_core.sh` vs
|
|
||||||
`ci/test_integration.sh`).
|
|
||||||
- **Helpers are well-designed.** `test/helpers/kernel_fixture.dart` boots a real
|
|
||||||
`KernelServices` with in-memory themes/i18n and `autoStartDaemonClient: false` — no
|
|
||||||
real socket, temp-dir scoped, proper `dispose()`. `FakeDaemonClient` is a clean stub.
|
|
||||||
- **Error-branch discipline.** `pql_commands_errors_test.dart` /
|
|
||||||
`git_commands_errors_test.dart` deliberately point the toolchain at a non-existent
|
|
||||||
binary to drive catch-branches the happy path can't reach — table-driven, with
|
|
||||||
`reason:` tags.
|
|
||||||
- **OS-dialog avoidance is handled correctly.** `welcome/dialog_test.dart` mocks the
|
|
||||||
`clide/window` MethodChannel to throw `MissingPluginException`, exercising the fallback
|
|
||||||
path *without* spawning a native file picker.
|
|
||||||
- **Startup gate.** `ci/smoke_bundle.sh` builds the real release bundle and runs it
|
|
||||||
under xvfb for 5s, correctly interpreting `timeout` exit codes (124/143 = healthy).
|
|
||||||
- **Coverage gate is honest.** `ci/coverage_gate.sh` is a self-contained awk parser (no
|
|
||||||
`lcov` dependency), ratchets only upward, and `exit 2` distinguishes "missing data"
|
|
||||||
from "below floor."
|
|
||||||
|
|
||||||
### Findings
|
|
||||||
|
|
||||||
- **[Major] PTY tests are excluded from coverage.** `ci/test.sh:13` runs
|
|
||||||
`flutter test --coverage --exclude-tags forkpty`; the `forkpty` tests run separately
|
|
||||||
via `dart test` with no `--coverage`. So `lib/src/pty/native_pty.dart` — the
|
|
||||||
highest-risk native code in the repo — is barely in the measured denominator. The
|
|
||||||
92.75% number overstates coverage of the riskiest file.
|
|
||||||
- **[Major] Wall-clock sleeps will flake.** `test/files/watcher_test.dart:67-82` uses
|
|
||||||
fixed `Future.delayed`; `test/pty/session_test.dart:71` polls 50×100ms and `:65` uses a
|
|
||||||
bare `500ms` settle. `session_test.dart`'s `timeout(5s, onTimeout: () {})` (`:48`)
|
|
||||||
*swallows* the timeout — a never-producing PTY proceeds to a confusing assertion
|
|
||||||
failure rather than a clear timeout.
|
|
||||||
- **[Major] `make push-check` does not run integration tests.** `push-check:
|
|
||||||
decisions-validate test-core test test-a11y coverage-gate` — `test-integration` and
|
|
||||||
`smoke-bundle` are omitted. A boot-order regression sails through.
|
|
||||||
- **[Minor] `flutter analyze --no-fatal-infos` in `ci/test.sh:9`** contradicts the
|
|
||||||
stated "fail-on-warning, clean board" discipline.
|
|
||||||
- **[Minor] Integration tests run one-file-at-a-time** to dodge a batch "Unable to start
|
|
||||||
the app" error — each invocation re-boots the engine (slow), and the workaround masks
|
|
||||||
whether the batch failure is environmental or a real teardown leak.
|
|
||||||
- **[Minor] Golden CI config disabled.** Only platform goldens run; a Linux-only CI
|
|
||||||
never validates the macOS goldens, and stale `test/goldens/failures/*.png` artifacts
|
|
||||||
are committed to the repo.
|
|
||||||
- **[Minor] `test_core.sh` timeout kill is best-effort** — the `pkill -9 -f` pattern
|
|
||||||
match is redundant noise next to the real `setsid` + `timeout --kill-after` safety net.
|
|
||||||
- **[Minor] `git/client_test.dart` depends on the ambient `git` binary**, not the
|
|
||||||
vendored dugite — the suite passes/fails on the host git version.
|
|
||||||
|
|
||||||
### Recommendations
|
|
||||||
|
|
||||||
**Quick wins:** add `test-integration` (and `smoke-bundle`) to `push-check` — the single
|
|
||||||
highest-value change; run the `forkpty` tests with `--coverage`; drop `--no-fatal-infos`;
|
|
||||||
gitignore `test/goldens/failures/`; make `onTimeout` callbacks `fail()`.
|
|
||||||
|
|
||||||
**Larger efforts:** replace fixed sleeps with event-driven waits
|
|
||||||
(`expectLater(stream, emits(...))`); add a macOS golden CI matrix entry or document
|
|
||||||
goldens as advisory; consider a coverage-exclusion allowlist for genuinely-unreachable
|
|
||||||
defensive branches rather than chasing the last lines with filler tests.
|
|
||||||
|
|
||||||
### Scorecard
|
|
||||||
|
|
||||||
| Dimension | Score | Justification |
|
|
||||||
|---|---|---|
|
|
||||||
| Coverage quality | 4/5 | Tests are meaningful even in "mop-up" files; docked because PTY/FFI is outside the measured number. |
|
|
||||||
| Test reliability / flakiness | 3/5 | Fixed wall-clock sleeps and a swallowed timeout are latent intermittent failures. |
|
|
||||||
| Gate trustworthiness | 3/5 | Coverage gate and smoke bundle are well-built, but `push-check` omits integration tests. |
|
|
||||||
| Test maintainability | 5/5 | Shared fixtures, consistent structure, table-driven error suites, clear doc comments. |
|
|
||||||
| Regression-catching power | 4/5 | Real boot-path integration + smoke + a11y + goldens; weakened by single-OS goldens and PTY coverage gaps. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. UX & accessibility — UX & Accessibility Expert
|
|
||||||
|
|
||||||
### Executive summary
|
|
||||||
|
|
||||||
clide has an unusually disciplined *foundation* for a solo pre-v2.0 project: a coherent
|
|
||||||
semantic design-token system, a WCAG-AA contrast gate wired into CI, and i18n/semantic
|
|
||||||
contract tests. That foundation is the biggest strength. The biggest risk is that
|
|
||||||
**keyboard operability is largely unimplemented below the foundation** — the project's
|
|
||||||
own core interaction primitive (`ClideTappable`) is mouse-only, the command palette has
|
|
||||||
no arrow-key navigation or Escape, and there is no focus-traversal wiring across panels.
|
|
||||||
For a keyboard-first power-user dev tool, this is a critical gap that the a11y test suite
|
|
||||||
does not catch because the tests assert *structural* presence (Semantics nodes exist)
|
|
||||||
rather than *operability* (can you actually drive it from the keyboard).
|
|
||||||
|
|
||||||
### Strengths
|
|
||||||
|
|
||||||
- **Semantic token system is real and enforced.** `lib/kernel/src/theme/tokens.dart`
|
|
||||||
defines ~65 named surface tokens; widgets consume `ClideTheme.of(context).surface`
|
|
||||||
rather than raw colors. The resolver provides defaults so partial themes still produce
|
|
||||||
a complete `SurfaceTokens`.
|
|
||||||
- **Contrast gate is genuine WCAG math, run per-theme.** `lib/kernel/src/theme/
|
|
||||||
contrast.dart` implements real relative-luminance ratio with alpha pre-compositing
|
|
||||||
against neutral grey (`contrast.dart:31-37`) — semi-transparent tokens can't spuriously
|
|
||||||
pass.
|
|
||||||
- **Semantics are present on composed widgets.** `ClideButton` wraps
|
|
||||||
`Semantics(button: true, enabled:, label:, hint:, onTap:)`; panels set
|
|
||||||
`container: true, explicitChildNodes: true` with landmark labels.
|
|
||||||
- **State coverage exists in data panels.** `git_panel_view.dart:86-104` handles error,
|
|
||||||
loading, and empty ("working tree clean") states distinctly; `file_tree_view.dart`
|
|
||||||
handles error + loading.
|
|
||||||
- **Manual a11y discipline is documented.** `docs/testing/a11y-manual.md` prescribes a
|
|
||||||
per-tier Orca/VoiceOver pass and is honest about why prose quality can't be automated.
|
|
||||||
- **Disabled state is handled at the cursor level.** `clide_button.dart:41` switches to
|
|
||||||
`SystemMouseCursors.forbidden` and drops the semantic `onTap` when `onPressed == null`.
|
|
||||||
|
|
||||||
### Findings
|
|
||||||
|
|
||||||
- **[Critical] `ClideTappable` is mouse-only — no `Focus`, no keyboard activation.**
|
|
||||||
`lib/widgets/src/clide_tappable.dart:37-54` is `MouseRegion` + `GestureDetector` only.
|
|
||||||
It is the base for `ClideButton`, `_WinBtn`, `_RecentProjectRow`, `_ActionRow`, and
|
|
||||||
most builtin list items. None can receive Tab focus or be activated with Enter/Space.
|
|
||||||
The keyboard-traversal test only passes because it manually wraps the button in an
|
|
||||||
external `Focus` node — it tests that the widget doesn't *block* focus, not that it
|
|
||||||
*accepts* it.
|
|
||||||
- **[Critical] Command palette is not keyboard-navigable.** `clide_palette.dart` —
|
|
||||||
`onSubmitted` only ever invokes `filtered.first` (`:77-80`); no up/down handling, no
|
|
||||||
selected index, no selection highlight, no Escape handler.
|
|
||||||
- **[Major] No focus-traversal wiring between panels.** `FocusTracker`
|
|
||||||
(`lib/kernel/src/focus.dart`) tracks an active *contribution id* for the `clide active`
|
|
||||||
CLI, but is not Flutter `FocusScope`/`FocusTraversalGroup` integration. Nothing
|
|
||||||
establishes Tab order across sidebar → workspace → context.
|
|
||||||
- **[Major] Drag-resize handles have no keyboard equivalent — parity gap.**
|
|
||||||
`drag_resize.dart` and `app.dart:870-912` are pure `Listener` pointer handlers, with no
|
|
||||||
Semantics node at all. Per "User/Claude parity", panel sizing should have a CLI
|
|
||||||
affordance; none is evident.
|
|
||||||
- **[Major] Single global `KeyboardListener` is a fragile keybinding architecture.**
|
|
||||||
`app.dart:90-148` routes all shortcuts through one root `KeyboardListener` — no
|
|
||||||
per-context scoping, will conflict with text-input fields.
|
|
||||||
`KeybindingResolver.fromKeyEvent` keys off layout-dependent `logicalKey.keyLabel`.
|
|
||||||
- **[Major] Text scale is the *only* in-app a11y accommodation, and it's hidden.**
|
|
||||||
`app.dart:122-138` implements Ctrl +/-/0 text scaling but it's undiscoverable. No
|
|
||||||
high-contrast toggle, no reduced-motion handling, no focus-ring rendering anywhere.
|
|
||||||
- **[Minor] Contrast gate covers only 11 token pairs** — omits `globalTextMuted` (muted
|
|
||||||
text is everywhere), the `status*` foregrounds, syntax tokens on `panelBackground`, and
|
|
||||||
`panelActiveBorder`.
|
|
||||||
- **[Minor] ~43 hardcoded-color sites bypass the token system** — some defensible (ANSI
|
|
||||||
palette), but the modal/palette shadow and window-control colors won't adapt to the
|
|
||||||
`paper` light theme.
|
|
||||||
- **[Minor] Hover state is inconsistent and not paired with focus** — every interactive
|
|
||||||
widget reimplements its own `_hover` bool; none render a focus indicator.
|
|
||||||
- **[Minor] `_LeftHatContent` is dead code** — `app.dart:281-292` always returns
|
|
||||||
`SizedBox.shrink()`.
|
|
||||||
|
|
||||||
### Recommendations
|
|
||||||
|
|
||||||
**Quick wins:** add a `Focus` + `Actions`/`Shortcuts` (Enter/Space → onTap) wrapper and
|
|
||||||
a focus-ring inside `ClideTappable` — fixes the [Critical] for every button/list-item at
|
|
||||||
once; add arrow-key + Escape + selected-index to `ClidePalette` (copy the existing
|
|
||||||
`_ProjectSwitcherDropdown` `onKeyEvent` pattern at `app.dart:446-452`); expand
|
|
||||||
`canonicalPairs`; surface text-zoom and theme switching in the palette; tokenize the
|
|
||||||
modal shadow and window-control colors.
|
|
||||||
|
|
||||||
**Larger efforts:** establish a real focus-traversal model and integrate `FocusTracker`
|
|
||||||
with Flutter's focus system; replace the root `KeyboardListener` with scoped
|
|
||||||
`Shortcuts`/`Actions` and move off `keyLabel`; add keyboard operability + Semantics to
|
|
||||||
drag-resize handles plus a `clide panel resize` CLI; add an a11y test tier that asserts
|
|
||||||
*operability*, not just Semantics presence.
|
|
||||||
|
|
||||||
### Scorecard
|
|
||||||
|
|
||||||
| Dimension | Score | Justification |
|
|
||||||
|---|---|---|
|
|
||||||
| Interaction model | 2/5 | Coherent slot/panel structure and good drag-resize *with a mouse*, but keyboard operability is largely unbuilt. |
|
|
||||||
| Accessibility | 3/5 | Genuine contrast gate, Semantics on composed widgets, i18n contract tests — but keyboard operability and focus order are not implemented. |
|
|
||||||
| Visual consistency | 4/5 | Strong semantic token system consumed consistently; a few hardcoded-color sites are real theme-adaptation bugs. |
|
|
||||||
| Discoverability | 2/5 | Command palette isn't keyboard-navigable; accommodations are undiscoverable; no in-app keybinding reference. |
|
|
||||||
| State coverage | 3/5 | Data panels and dialogs handle loading/error/empty; but no focus states anywhere and no reduced-motion handling. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Code quality & craft — Senior Dart/Flutter Engineer
|
|
||||||
|
|
||||||
### Executive summary
|
|
||||||
|
|
||||||
clide is, for a solo pre-v2.0 project, in genuinely good shape. The core subsystems (IPC
|
|
||||||
envelope, daemon dispatch, git client, PTY) are small, single-responsibility,
|
|
||||||
well-typed, and consistent. `flutter analyze` is clean for `lib/` — the 9 reported issues
|
|
||||||
are all in `test/`, none are suppressions. The biggest strength is the FFI/PTY layer:
|
|
||||||
`lib/src/pty/native_pty.dart` shows real systems-programming discipline (pre-fork
|
|
||||||
allocation, errno captured before `free`, isolate-teardown ordering documented and
|
|
||||||
correct). The biggest risk is concentrated in two places: a genuine isolate-leak race in
|
|
||||||
`SchedulerService`, and the large vendored-but-owned `lib/src/terminal/` xterm.dart fork
|
|
||||||
(~7k LOC) which carries a different style, commented-out `print`s, and dangling TODOs
|
|
||||||
that the project's own "lib is owned, not vendored" rule says must be held to the same
|
|
||||||
bar.
|
|
||||||
|
|
||||||
### Strengths
|
|
||||||
|
|
||||||
- **PTY/FFI layer is excellent.** `native_pty.dart:129-145` force-resolves FFI
|
|
||||||
trampolines and pre-allocates *all* native memory before `forkpty()`.
|
|
||||||
`native_pty.dart:171-177` captures `errno` before `_freeAll` because `free()` can
|
|
||||||
clobber it. `close()` (367-397) documents and implements the kill→EOF→close ordering
|
|
||||||
to avoid fd-reuse races. The child branch touches no Dart heap.
|
|
||||||
- **IPC envelope is clean and idiomatic** — `lib/src/ipc/envelope.dart` uses a `sealed`
|
|
||||||
class hierarchy, named constructors, a private unifying constructor, and conditional
|
|
||||||
map keys. Decode is total over the type discriminant.
|
|
||||||
- **Typed, meaningful errors.** `PtyException` carries `op` + optional `errno`;
|
|
||||||
`GitException` carries `stderr`; `errnoToIpcError` maps POSIX errno to actionable IPC
|
|
||||||
error kinds. Errors are values, not strings.
|
|
||||||
- **Resource lifecycle is taken seriously across most subsystems.** `FileWatcher.stop()`
|
|
||||||
cancels the subscription *and* closes the controller; `withBuffer`/`setWinsize` in
|
|
||||||
`libc.dart` use `try/finally` around every native allocation. 45 files define
|
|
||||||
`dispose`/`close`.
|
|
||||||
- **The `DaemonEventSink` interface** keeps the dependency graph pointing the right way
|
|
||||||
(server→subsystems) and is documented as such.
|
|
||||||
- **The one `ignore_for_file` (`libc.dart:11-27`) is exemplary** — textbook FFI case,
|
|
||||||
multi-paragraph justification exactly as CLAUDE.md requires.
|
|
||||||
|
|
||||||
### Findings
|
|
||||||
|
|
||||||
- **[Major] Isolate-leak race in `SchedulerService._startTicker`** — `scheduler.dart:71`:
|
|
||||||
`Isolate.spawn(...).then((iso) => _isolate = iso)`. If `_stopTicker()` runs before the
|
|
||||||
spawn future completes, `_isolate` is still null, nothing is killed, and the
|
|
||||||
just-spawned isolate (with its `Timer.periodic`) leaks. `native_pty.dart` solved
|
|
||||||
exactly this with `_readerReady`.
|
|
||||||
- **[Major] `lib/src/terminal/` held below the project's own bar.** Carries
|
|
||||||
commented-out `print()` debugging (`custom_text_edit.dart:244-275`), dangling TODOs
|
|
||||||
(`parser.dart:110-113`, `keytab.dart:91`), a 1137-line `parser.dart`, and the only
|
|
||||||
`// ignore: invalid_use_of_protected_member` in the repo (`terminal_view.dart:363`).
|
|
||||||
Either it's genuinely vendored (belongs in `native/` or documented as frozen) or it's
|
|
||||||
owned (needs the cleanup pass).
|
|
||||||
- **[Minor] Empty `catch (_) {}` swallows in `tree_sitter_ffi.dart:197,206`** —
|
|
||||||
`DynamicLibrary.open` failures silently discarded; caller gets a bare `null` with no
|
|
||||||
diagnostic about *why*. Syntax highlighting silently not working is a support
|
|
||||||
headache.
|
|
||||||
- **[Minor] Empty `catch (_) {}` in `test_app.dart:271,311`** — `:271` swallows a
|
|
||||||
theme-load failure the harness exists to detect.
|
|
||||||
- **[Minor] Dead alias in `libc.dart:201-202`** — `typedef Cmsghdr = CmsghdrLinux;`
|
|
||||||
flagged "backward compatibility"; CLAUDE.md forbids backwards-compat hacks in a solo
|
|
||||||
repo.
|
|
||||||
- **[Minor] `// ignore: unused_field` in `editor_controller.dart:25`** "kept for future
|
|
||||||
subscription changes" — speculative retention; the no-suppression rule wants it fixed,
|
|
||||||
not silenced.
|
|
||||||
- **[Minor] Magic numbers in hot FFI paths.** `native_pty.dart` inlines `0x0001
|
|
||||||
// POLLIN`, `28 /* SIGWINCH */`, `4 /* EINTR */`, `9 /* EBADF */` — but `libc.dart`
|
|
||||||
already has a constants section and `errno_mapping.dart` has `PosixErrno.ebadf`.
|
|
||||||
- **[Minor] `git_commands.dart` has ~16 near-identical handler bodies** — a
|
|
||||||
`_guarded(req, () async {...})` helper would remove ~60 lines of structural
|
|
||||||
duplication. Borderline.
|
|
||||||
|
|
||||||
### Recommendations
|
|
||||||
|
|
||||||
**Quick wins:** fix the `SchedulerService` spawn race (track the spawn future like
|
|
||||||
`NativePty._readerReady`); replace the three silent `catch (_)` in `tree_sitter_ffi.dart`
|
|
||||||
with a logged last-error; delete the `Cmsghdr` alias and the `unused_field` suppression;
|
|
||||||
have the PTY layer consume `libc.dart` constants / `PosixErrno` instead of inline hex.
|
|
||||||
|
|
||||||
**Larger efforts:** decide the status of `lib/src/terminal/` — formally vendor it
|
|
||||||
(freeze, document, decision-record) or do the cleanup sweep; optionally a `_guarded`
|
|
||||||
helper for `git_commands.dart` (check whether `files_commands` / `editor_commands` share
|
|
||||||
the shape).
|
|
||||||
|
|
||||||
### Scorecard
|
|
||||||
|
|
||||||
| Dimension | Score | Justification |
|
|
||||||
|---|---|---|
|
|
||||||
| Idiomatic Dart | 4/5 | Sealed classes, named ctors, records, `const`, immutability used well; the vendored terminal tree pulls the average down. |
|
|
||||||
| Error handling | 4/5 | Typed errors with context everywhere in core; a few silent `catch (_)` in the FFI loader and test harness cost the 5th point. |
|
|
||||||
| Naming & readability | 4/5 | Clear, intention-revealing names; comments earn their place; inline magic numbers are the main blemish. |
|
|
||||||
| Consistency across subsystems | 3/5 | IPC/git/files/pty are uniform; `lib/src/terminal/` is a different codebase in style; PTY duplicates constants `libc.dart` owns. |
|
|
||||||
| Resource/lifecycle safety | 4/5 | `try/finally` around native allocs, controllers closed, subscriptions cancelled; the one real defect is the `SchedulerService` race. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Security & supply chain — Security Engineer
|
|
||||||
|
|
||||||
### Executive summary
|
|
||||||
|
|
||||||
clide's security posture is **above average for a solo pre-v2.0 project**. All
|
|
||||||
subprocess calls use `Process.run`/`Process.start` with argument *lists* (no shell
|
|
||||||
interpolation), the IPC transport is a per-user Unix socket (not a TCP port), and there
|
|
||||||
is an explicit `path_safety` module with a containment check. The single biggest strength
|
|
||||||
is the disciplined no-shell subprocess layer. The single biggest risk is
|
|
||||||
**untrusted-workspace code execution via toolchain resolution**
|
|
||||||
(`toolchain_paths.dart:79`): a malicious repo can ship a `native/dugite/bin/git`
|
|
||||||
executable that clide will resolve and run. Secondary real issues: path-safety does not
|
|
||||||
defend against symlink escape, and IPC command args are largely unvalidated/un-bounded.
|
|
||||||
Supply-chain hygiene is mostly good but `licenses.yaml` has drifted from `pubspec.yaml`
|
|
||||||
and native binaries are committed without SHA pinning.
|
|
||||||
|
|
||||||
### Strengths
|
|
||||||
|
|
||||||
- **No-shell subprocess execution.** `GitClient._run` (`client.dart:210`),
|
|
||||||
`PqlClient._run` (`client.dart:165`), and the PTY layer all pass `List<String>` args
|
|
||||||
directly. Classic command injection is structurally prevented.
|
|
||||||
- **Toolchain uses resolved absolute paths** — git/pql/tmux resolved once to absolute
|
|
||||||
paths and reused.
|
|
||||||
- **Path containment check exists and is used.** `resolveUnderRoot`
|
|
||||||
(`path_safety.dart:21`) collapses `..`/`.` without touching the filesystem and enforces
|
|
||||||
a prefix check with a separator guard. `files.read`/`files.ls` both call it.
|
|
||||||
- **IPC is a per-user Unix socket, not a network listener.** No `ServerSocket` over TCP
|
|
||||||
anywhere; the default runtime path is in-process, eliminating the socket attack
|
|
||||||
surface in the shipped app.
|
|
||||||
- **PTY FFI memory discipline** — all native memory allocated before `forkpty()`, `errno`
|
|
||||||
captured before `free()`, freed on every path.
|
|
||||||
- **`pubspec.lock` is committed**, deps use exact pins (no carets), `licenses.yaml`
|
|
||||||
exists with per-dep purpose/license.
|
|
||||||
|
|
||||||
### Findings
|
|
||||||
|
|
||||||
- **[Critical] Malicious workspace can plant a git binary that clide executes.**
|
|
||||||
`toolchain_paths.dart:79-84` builds `'$workspaceRoot/native/dugite/bin'` and runs
|
|
||||||
`_firstExisting(['$dugite/git'])`; if that file exists it becomes the git binary for
|
|
||||||
all `GitClient` calls, **before** falling back to PATH. An attacker commits an
|
|
||||||
executable at `native/dugite/bin/git`; clide runs it on the first `git.status` (which
|
|
||||||
fires automatically on workspace open). Arbitrary code execution from merely opening a
|
|
||||||
repo. The `native/dugite` convention should resolve relative to the *clide install
|
|
||||||
dir*, never the workspace root.
|
|
||||||
- **[Major] Path-safety does not defend against symlink escape.** `path_safety.dart:
|
|
||||||
35-51` explicitly does not resolve symlinks, and the filesystem layer
|
|
||||||
(`files_commands.dart:81-85`) never does either. A repo symlink `config -> /etc/shadow`
|
|
||||||
passes the containment check (the *link path* is under root) and clide reads the
|
|
||||||
target. Fix: after `resolveUnderRoot`, `resolveSymbolicLinksSync()` and re-verify
|
|
||||||
containment.
|
|
||||||
- **[Major] IPC command arguments are unvalidated and unbounded.**
|
|
||||||
`DaemonDispatcher.dispatch` (`dispatcher.dart:26`) and `IpcRequest.fromJson`
|
|
||||||
(`envelope.dart:49`) do no schema validation. No size limit on `files.read`, no count
|
|
||||||
cap on `git.log`, no check that `git.checkout`'s `branch` (`git_commands.dart:240`)
|
|
||||||
isn't a `-`-prefixed flag. `git diff`/`stage` use `--` separators (good), but
|
|
||||||
`checkout(branch)` and `push(remote, branch)` do not — argument injection
|
|
||||||
(`git checkout --upload-pack=...`) is possible.
|
|
||||||
- **[Minor] macOS entitlements disable library validation.**
|
|
||||||
`macos/Runner/Release.entitlements` sets `disable-library-validation` = true with no
|
|
||||||
App Sandbox entitlement. Arguably needed for the `dlopen` of `libtree-sitter.so`, but
|
|
||||||
combined with no sandbox a compromised process has full user-level filesystem access.
|
|
||||||
- **[Minor] `licenses.yaml` has drifted from `pubspec.yaml`.** Lists dev-dep `test` at
|
|
||||||
`1.25.8` but `pubspec.yaml:60` pins `1.30.0`; lists a `lints 5.0.0` not in
|
|
||||||
`pubspec.yaml` at all. The two-step-commit guardrail is being violated.
|
|
||||||
- **[Minor] Native binaries committed without SHA pinning.** `native/linux-x64/` has
|
|
||||||
`libtree-sitter.so` (24 MB) and `ptyc` (22 KB) committed with no `SHA256SUMS` manifest.
|
|
||||||
CLAUDE.md says native deps are "pinned by SHA"; that pinning is not evidenced.
|
|
||||||
- **[Informational] No secrets service** — clide stores no tokens; git auth is delegated
|
|
||||||
to the system credential helper. The right call; noted so the absence reads as
|
|
||||||
deliberate.
|
|
||||||
- **[Informational] Lua runtime is a stub** — `lib/lua/src/host.dart` is Tier-0. Design
|
|
||||||
intent (strip `io`/`os.execute`/`package.loadlib`/`debug`) is sound; re-assess at Tier
|
|
||||||
6 — sandbox-escape via FFI re-entry will be the concern.
|
|
||||||
|
|
||||||
### Recommendations
|
|
||||||
|
|
||||||
**Quick wins:** fix toolchain resolution to resolve `native/dugite` against
|
|
||||||
`Platform.resolvedExecutable`'s directory, never `workspaceRoot` (closes the Critical);
|
|
||||||
add symlink re-check in `files.read`/`files.ls`; reconcile `licenses.yaml` with
|
|
||||||
`pubspec.yaml`; reject `-`-prefixed values for `branch`/`remote`/`path` args (or use
|
|
||||||
`--` everywhere, including `checkout`).
|
|
||||||
|
|
||||||
**Larger efforts:** schema-validate the IPC surface with typed arg schemas + size/count
|
|
||||||
bounds; add a committed `native/SHA256SUMS` verified by `make` and CI; revisit macOS
|
|
||||||
sandboxing (App Sandbox with explicit exceptions); security-review the Lua FFI boundary
|
|
||||||
and capability table before Tier 6 ships.
|
|
||||||
|
|
||||||
### Scorecard
|
|
||||||
|
|
||||||
| Area | Rating | Justification |
|
|
||||||
|---|---|---|
|
|
||||||
| Subprocess safety | 2/5 | No-shell arg lists are excellent, but the workspace-relative dugite path is a real RCE; argument-injection on `checkout`/`push` unmitigated. |
|
|
||||||
| IPC input validation | 3/5 | Per-user Unix socket + in-process default sharply limits exposure, but zero arg-schema validation and no size/count bounds. |
|
|
||||||
| Path/filesystem safety | 3/5 | Real containment check that's actually wired in, undermined by the unhandled symlink-escape gap. |
|
|
||||||
| Dependency/supply-chain hygiene | 3/5 | Exact pins, committed lockfile, documented deps — but `licenses.yaml` drift and missing SHA manifest for committed native binaries. |
|
|
||||||
| Secrets & sandboxing | 3/5 | Correctly delegates secrets; Lua sandbox is only a stub; macOS runs with library validation off and no App Sandbox. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Docs, governance & DX — TPM / Developer-Experience Consultant
|
|
||||||
|
|
||||||
### Executive summary
|
|
||||||
|
|
||||||
clide runs an unusually disciplined governance system for a solo-dev pre-v2.0 project:
|
|
||||||
67 decision records across six domains, with a parser-validated DQR structure, anchored
|
|
||||||
cross-references, and a `make decisions-validate` gate wired into pre-push. The biggest
|
|
||||||
strength is that the DQR system is genuinely *alive* — questions get resolved with dated
|
|
||||||
amendments, superseded decisions are marked, and decisions cite the commits that
|
|
||||||
implement them. The biggest risk is **documentation drift in the narrative docs**:
|
|
||||||
`README.md` and `docs/initial-plan.md` describe an architecture (Go sidecar, `ptyc/` C
|
|
||||||
helper, `app/` subdirectory, separate daemon) that three major decisions (D-5, D-56, the
|
|
||||||
FFI pivot) have since dissolved. A new contributor reading the README first would build
|
|
||||||
a wrong mental model.
|
|
||||||
|
|
||||||
### Strengths
|
|
||||||
|
|
||||||
- **DQR system is maintained, not ornamental.** Resolved questions carry dated
|
|
||||||
resolution lines pointing to the deciding D-record (`questions/architecture.md:39`
|
|
||||||
Q-6→D-57). D-40 carries a `[SUPERSEDED]` tag and an amendment line.
|
|
||||||
- **Decisions are linked to code and commits.** D-67 (`decisions/process.md:61`) cites
|
|
||||||
implementing commits `01a99ed`, `d162ba2`. D-66 references `ci/test.sh` by path.
|
|
||||||
- **Governance migration was done cleanly** — the `decisions/` → `governance/`
|
|
||||||
restructure updated cross-references and the auto-generated index.
|
|
||||||
- **Commit discipline is real.** `git log` shows imperative subjects, no Conventional
|
|
||||||
Commits prefixes, ticket refs, logical scoping — exactly what `git-commit/SKILL.md`
|
|
||||||
prescribes.
|
|
||||||
- **`licenses.yaml` is thorough** — all six runtime Dart deps present, plus fonts/native
|
|
||||||
libs, with purpose justifications. *(Note: the Security reviewer found version drift
|
|
||||||
in this file — see Finding above; the two reviewers examined different rows.)*
|
|
||||||
- **Makefile is self-documenting** (`##` help annotations) and matches `CLAUDE.md`.
|
|
||||||
|
|
||||||
### Findings
|
|
||||||
|
|
||||||
- **[Critical] `docs/initial-plan.md` is badly stale.** The "north-star" doc (linked
|
|
||||||
from `CLAUDE.md:14` and `README.md:44`) still describes a Go sidecar
|
|
||||||
(`initial-plan.md:4,55,189`), `clide --daemon` long-running process (`:162-164`),
|
|
||||||
`app/` subdirectory layout (`:184-204`), and `project.yaml` (`:172`) — all contradicted
|
|
||||||
by D-5, D-56, and the single-package-at-root reality. Nothing flags it as historical.
|
|
||||||
- **[Critical] `README.md` describes a dissolved architecture.** `README.md:10`
|
|
||||||
documents `ptyc/` as a live component; `README.md:36` lists `make ptyc-build`. The
|
|
||||||
`ptyc/` directory does not exist, the Makefile has no such target, and the CHANGELOG's
|
|
||||||
own Unreleased section records ptyc's removal.
|
|
||||||
- **[Major] `README.md:44` links to `decisions/`** — a directory that no longer exists
|
|
||||||
(migrated to `governance/`). Dead link in the primary onboarding doc.
|
|
||||||
- **[Major] CHANGELOG has duplicate subsection headings in `[Unreleased]`.** Three
|
|
||||||
`### Changed` blocks (`CHANGELOG.md:100, 114, 168`), two `### Fixed`, two `### Removed`
|
|
||||||
in the 2.0.0 section. Keep a Changelog 1.1.0 expects one of each per release.
|
|
||||||
- **[Major] No `CONTRIBUTING.md` or onboarding doc.** For a project "intended to ship
|
|
||||||
publicly to other developers," there is no contributor guide; the build/test story is
|
|
||||||
scattered across `CLAUDE.md` (Claude-oriented), `README.md` (partly wrong), and
|
|
||||||
Makefile help.
|
|
||||||
- **[Major] Coverage-floor governance contradicts itself.** D-66 (`testing.md:65`) says
|
|
||||||
the floor lives at `coverage/floor.txt` starting "≈35%"; `CHANGELOG.md:44-46` says it's
|
|
||||||
in `pubspec.yaml` `coverage_floor:` starting at 34%; the latest commit is `9030e56
|
|
||||||
hold coverage_floor fixed at 90`. Three sources, three mechanisms/values. D-66 was
|
|
||||||
never amended.
|
|
||||||
- **[Minor] `ci/release.sh` is a stub that still references goreleaser/sidecar** — Go
|
|
||||||
tooling for a project with no Go.
|
|
||||||
- **[Minor] CHANGELOG `[2.0.0] — 2026-05-03` dating** — the v2.0.0 tag is dated
|
|
||||||
2026-05-03 but the enormous Unreleased section represents ~80 commits of post-tag work
|
|
||||||
with no interim version.
|
|
||||||
- **[Minor] Stale-ish open questions** — Q-25 (body text face) is de facto resolved by
|
|
||||||
D-43/D-44 and the shipped impl; Q-1/Q-2/Q-3 ("defer until Tier 1 is in real use") are
|
|
||||||
due for triage now that Tier 1 has shipped.
|
|
||||||
- **[Minor] Skills system is coherent but undocumented as a set** — eight skills under
|
|
||||||
`.claude/skills/`, no index.
|
|
||||||
|
|
||||||
### Recommendations
|
|
||||||
|
|
||||||
**Quick wins:** rewrite `README.md`'s `ptyc/` sections and fix the `decisions/` link;
|
|
||||||
banner `docs/initial-plan.md` as historical (or split out a current
|
|
||||||
`docs/architecture.md`); merge the duplicate changelog subsection headings; amend D-66 to
|
|
||||||
reflect the floor's actual location/mechanism/value with a dated amendment line; triage
|
|
||||||
Q-1/2/3/25.
|
|
||||||
|
|
||||||
**Larger efforts:** write a human-facing `CONTRIBUTING.md` (clone →
|
|
||||||
`make hooks && flutter pub get` → `make test` → DQR workflow → commit conventions); cut
|
|
||||||
an interim release to drain the ~80-commit Unreleased backlog; add a
|
|
||||||
`.claude/skills/README.md` inventory; establish a periodic governance sweep (the repo
|
|
||||||
even has a `clean-house` skill for exactly this).
|
|
||||||
|
|
||||||
### Scorecard
|
|
||||||
|
|
||||||
| Area | Score | Justification |
|
|
||||||
|---|---|---|
|
|
||||||
| Governance discipline | 4/5 | DQR system genuinely maintained — but D-66 drift and untriaged Tier-1-era questions show the sweep cadence lags the code. |
|
|
||||||
| Documentation accuracy | 2/5 | Both primary onboarding docs describe a dissolved Go-sidecar/ptyc/daemon architecture; `CLAUDE.md` is accurate by contrast. |
|
|
||||||
| Changelog hygiene | 3/5 | Per-commit discipline is followed, but duplicate subsection headings violate the standard and an 80-commit Unreleased backlog undermines the format. |
|
|
||||||
| Contributor onboarding | 2/5 | No `CONTRIBUTING.md`; build story split across three docs, one wrong; `CLAUDE.md` is Claude-addressed, not human-addressed. |
|
|
||||||
| Convention adherence | 4/5 | Commit style, DQR claiming, `licenses.yaml` two-step rule demonstrably followed; docked for the changelog defects and the README gap. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Closing note
|
|
||||||
|
|
||||||
The recurring pattern across all six reviews: **clide's foundations are excellent and
|
|
||||||
its finishing is incomplete.** The extension contract, test helpers, FFI discipline,
|
|
||||||
governance system, and token system are all things most projects never get right. The
|
|
||||||
gaps — IPC not wired, keyboard not operable, docs describing a dead architecture, a
|
|
||||||
workspace-relative binary path — are all the kind of thing that happens when a fast-moving
|
|
||||||
solo project's implementation outruns its connective tissue. They are concentrated, not
|
|
||||||
diffuse, and the quick-win column above would close most of the critical ones in a few
|
|
||||||
focused days.
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# dartdoc configuration.
|
||||||
|
#
|
||||||
|
# clide is an application, not a published library — its lib/ API docs are not
|
||||||
|
# a consumed surface. The dart-doc CI gate (.github/workflows/test.yml) exists
|
||||||
|
# to catch real doc-comment defects: unresolved [symbol] references. That check
|
||||||
|
# stays strict (unresolved-doc-reference is NOT ignored).
|
||||||
|
#
|
||||||
|
# The "broken-link" category, by contrast, is pure rendering noise here:
|
||||||
|
# - The README is dartdoc's landing page; its repo-relative links
|
||||||
|
# ([CLAUDE.md], [docs/…], [LICENSE]) are correct on GitHub but don't resolve
|
||||||
|
# once copied into the generated API site.
|
||||||
|
# - The generated Phosphor icon font (lib/widgets/src/icons/phosphor_glyphs.g.dart,
|
||||||
|
# 1512 glyphs) produces broken cross-links to its own library page.
|
||||||
|
# Neither is a fixable doc-quality problem, so the category is ignored.
|
||||||
|
dartdoc:
|
||||||
|
ignore:
|
||||||
|
- broken-link
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
{
|
||||||
|
"name": "Settings — Appearance",
|
||||||
|
"shapes": {
|
||||||
|
"app-bg": { "type": "Rectangle", "left": 0, "top": 0, "width": 1180, "height": 720, "fillColor": "#17171E", "strokeColor": "#17171E" },
|
||||||
|
"app-hat": { "type": "Rectangle", "left": 0, "top": 0, "width": 1180, "height": 28, "fillColor": "#1B1B22", "strokeColor": "#1B1B22" },
|
||||||
|
"app-sidebar": { "type": "Rectangle", "left": 0, "top": 28, "width": 54, "height": 692, "fillColor": "#1B1B22", "strokeColor": "#1B1B22" },
|
||||||
|
|
||||||
|
"modal-shadow": { "type": "Rectangle", "left": 134, "top": 66, "width": 920, "height": 600, "fillColor": "#101015", "strokeColor": "#101015", "corners": [12, 12, 12, 12] },
|
||||||
|
"modal": { "type": "Rectangle", "left": 130, "top": 60, "width": 920, "height": 600, "fillColor": "#20202C", "strokeColor": "#3C445C", "corners": [10, 10, 10, 10] },
|
||||||
|
|
||||||
|
"modal-title": { "type": "Text", "left": 154, "top": 76, "text": "Settings", "fontColor": "#E6E8F2", "fontSize": 18 },
|
||||||
|
"modal-close": { "type": "Text", "left": 1020, "top": 74, "text": "✕", "fontColor": "#8890AC", "fontSize": 16 },
|
||||||
|
"header-divider": { "type": "Rectangle", "left": 130, "top": 104, "width": 920, "height": 1, "fillColor": "#343850", "strokeColor": "#343850" },
|
||||||
|
|
||||||
|
"rail": { "type": "Rectangle", "left": 130, "top": 105, "width": 228, "height": 554, "fillColor": "#1A1A24", "strokeColor": "#1A1A24", "corners": [0, 0, 0, 10] },
|
||||||
|
"rail-divider": { "type": "Rectangle", "left": 358, "top": 105, "width": 1, "height": 554, "fillColor": "#343850", "strokeColor": "#343850" },
|
||||||
|
|
||||||
|
"search-box": { "type": "Rectangle", "left": 144, "top": 118, "width": 200, "height": 28, "fillColor": "#242838", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"search-icon": { "type": "Ellipse", "left": 154, "top": 124, "width": 11, "height": 11, "fillColor": "#242838", "strokeColor": "#78809C" },
|
||||||
|
"search-icon-handle": { "type": "Rectangle", "left": 163, "top": 133, "width": 4, "height": 1, "fillColor": "#78809C", "strokeColor": "#78809C" },
|
||||||
|
"search-text": { "type": "Text", "left": 176, "top": 124, "text": "Search all settings…", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
|
||||||
|
"cat-editor": { "type": "Text", "left": 152, "top": 164, "text": "Editor", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-keymap": { "type": "Text", "left": 152, "top": 200, "text": "Keymap", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-appearance-sel": { "type": "Rectangle", "left": 130, "top": 228, "width": 228, "height": 32, "fillColor": "#2C3046", "strokeColor": "#2C3046" },
|
||||||
|
"cat-appearance-stripe": { "type": "Rectangle", "left": 130, "top": 228, "width": 3, "height": 32, "fillColor": "#78A0F8", "strokeColor": "#78A0F8" },
|
||||||
|
"cat-appearance": { "type": "Text", "left": 152, "top": 236, "text": "Appearance", "fontColor": "#E6E8F2", "fontSize": 14 },
|
||||||
|
"cat-claude": { "type": "Text", "left": 152, "top": 272, "text": "Claude", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-activity": { "type": "Text", "left": 152, "top": 308, "text": "Activity", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-terminal": { "type": "Text", "left": 152, "top": 344, "text": "Terminal", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-extensions": { "type": "Text", "left": 152, "top": 380, "text": "Extensions", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
|
||||||
|
"panel-title": { "type": "Text", "left": 382, "top": 120, "text": "Appearance", "fontColor": "#E6E8F2", "fontSize": 18 },
|
||||||
|
"panel-sub": { "type": "Text", "left": 382, "top": 148, "text": "Theme, contrast, and text size.", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
|
||||||
|
"grp-theme": { "type": "Text", "left": 382, "top": 186, "text": "THEME", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
"theme-scope-body": { "type": "Rectangle", "left": 1004, "top": 188, "width": 15, "height": 10, "fillColor": "#20202C", "strokeColor": "#7DD3A8", "corners": [2, 2, 2, 2] },
|
||||||
|
"theme-scope-tab": { "type": "Rectangle", "left": 1004, "top": 185, "width": 7, "height": 3, "fillColor": "#7DD3A8", "strokeColor": "#7DD3A8" },
|
||||||
|
|
||||||
|
"s1-card": { "type": "Rectangle", "left": 382, "top": 210, "width": 200, "height": 92, "fillColor": "#20202C", "strokeColor": "#78A0F8", "corners": [6, 6, 6, 6] },
|
||||||
|
"s1-side": { "type": "Rectangle", "left": 390, "top": 218, "width": 22, "height": 76, "fillColor": "#1A1A24", "strokeColor": "#1A1A24", "corners": [3, 3, 3, 3] },
|
||||||
|
"s1-bar": { "type": "Rectangle", "left": 418, "top": 218, "width": 156, "height": 14, "fillColor": "#242838", "strokeColor": "#242838", "corners": [3, 3, 3, 3] },
|
||||||
|
"s1-accent": { "type": "Rectangle", "left": 418, "top": 240, "width": 40, "height": 4, "fillColor": "#78A0F8", "strokeColor": "#78A0F8" },
|
||||||
|
"s1-l1": { "type": "Rectangle", "left": 418, "top": 254, "width": 120, "height": 4, "fillColor": "#E6E8F2", "strokeColor": "#E6E8F2" },
|
||||||
|
"s1-l2": { "type": "Rectangle", "left": 418, "top": 266, "width": 90, "height": 4, "fillColor": "#8890AC", "strokeColor": "#8890AC" },
|
||||||
|
"s1-l3": { "type": "Rectangle", "left": 418, "top": 278, "width": 110, "height": 4, "fillColor": "#8890AC", "strokeColor": "#8890AC" },
|
||||||
|
"s1-check-bg": { "type": "Ellipse", "left": 558, "top": 218, "width": 16, "height": 16, "fillColor": "#78A0F8", "strokeColor": "#78A0F8" },
|
||||||
|
"s1-check": { "type": "Text", "left": 561, "top": 219, "text": "✓", "fontColor": "#20202C", "fontSize": 11 },
|
||||||
|
"s1-name": { "type": "Text", "left": 390, "top": 306, "text": "clide", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
|
||||||
|
"s2-card": { "type": "Rectangle", "left": 598, "top": 210, "width": 200, "height": 92, "fillColor": "#1E1E1E", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"s2-side": { "type": "Rectangle", "left": 606, "top": 218, "width": 22, "height": 76, "fillColor": "#181818", "strokeColor": "#181818", "corners": [3, 3, 3, 3] },
|
||||||
|
"s2-bar": { "type": "Rectangle", "left": 634, "top": 218, "width": 156, "height": 14, "fillColor": "#252526", "strokeColor": "#252526", "corners": [3, 3, 3, 3] },
|
||||||
|
"s2-accent": { "type": "Rectangle", "left": 634, "top": 240, "width": 40, "height": 4, "fillColor": "#569CD6", "strokeColor": "#569CD6" },
|
||||||
|
"s2-l1": { "type": "Rectangle", "left": 634, "top": 254, "width": 120, "height": 4, "fillColor": "#D4D4D4", "strokeColor": "#D4D4D4" },
|
||||||
|
"s2-l2": { "type": "Rectangle", "left": 634, "top": 266, "width": 90, "height": 4, "fillColor": "#6A6A6A", "strokeColor": "#6A6A6A" },
|
||||||
|
"s2-l3": { "type": "Rectangle", "left": 634, "top": 278, "width": 110, "height": 4, "fillColor": "#6A6A6A", "strokeColor": "#6A6A6A" },
|
||||||
|
"s2-name": { "type": "Text", "left": 606, "top": 306, "text": "midnight", "fontColor": "#E6E8F2", "fontSize": 13 },
|
||||||
|
|
||||||
|
"s3-card": { "type": "Rectangle", "left": 814, "top": 210, "width": 200, "height": 92, "fillColor": "#F4F1EA", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"s3-side": { "type": "Rectangle", "left": 822, "top": 218, "width": 22, "height": 76, "fillColor": "#ECE7DB", "strokeColor": "#ECE7DB", "corners": [3, 3, 3, 3] },
|
||||||
|
"s3-bar": { "type": "Rectangle", "left": 850, "top": 218, "width": 156, "height": 14, "fillColor": "#FBF8F1", "strokeColor": "#FBF8F1", "corners": [3, 3, 3, 3] },
|
||||||
|
"s3-accent": { "type": "Rectangle", "left": 850, "top": 240, "width": 40, "height": 4, "fillColor": "#C14B2A", "strokeColor": "#C14B2A" },
|
||||||
|
"s3-l1": { "type": "Rectangle", "left": 850, "top": 254, "width": 120, "height": 4, "fillColor": "#1A1A1A", "strokeColor": "#1A1A1A" },
|
||||||
|
"s3-l2": { "type": "Rectangle", "left": 850, "top": 266, "width": 90, "height": 4, "fillColor": "#7A7468", "strokeColor": "#7A7468" },
|
||||||
|
"s3-l3": { "type": "Rectangle", "left": 850, "top": 278, "width": 110, "height": 4, "fillColor": "#7A7468", "strokeColor": "#7A7468" },
|
||||||
|
"s3-name": { "type": "Text", "left": 822, "top": 306, "text": "paper", "fontColor": "#E6E8F2", "fontSize": 13 },
|
||||||
|
|
||||||
|
"s4-card": { "type": "Rectangle", "left": 382, "top": 330, "width": 200, "height": 92, "fillColor": "#0C0C0C", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"s4-side": { "type": "Rectangle", "left": 390, "top": 338, "width": 22, "height": 76, "fillColor": "#000000", "strokeColor": "#000000", "corners": [3, 3, 3, 3] },
|
||||||
|
"s4-bar": { "type": "Rectangle", "left": 418, "top": 338, "width": 156, "height": 14, "fillColor": "#141414", "strokeColor": "#141414", "corners": [3, 3, 3, 3] },
|
||||||
|
"s4-accent": { "type": "Rectangle", "left": 418, "top": 360, "width": 40, "height": 4, "fillColor": "#FFC868", "strokeColor": "#FFC868" },
|
||||||
|
"s4-l1": { "type": "Rectangle", "left": 418, "top": 374, "width": 120, "height": 4, "fillColor": "#C8C8C8", "strokeColor": "#C8C8C8" },
|
||||||
|
"s4-l2": { "type": "Rectangle", "left": 418, "top": 386, "width": 90, "height": 4, "fillColor": "#5A5A5A", "strokeColor": "#5A5A5A" },
|
||||||
|
"s4-l3": { "type": "Rectangle", "left": 418, "top": 398, "width": 110, "height": 4, "fillColor": "#5A5A5A", "strokeColor": "#5A5A5A" },
|
||||||
|
"s4-name": { "type": "Text", "left": 390, "top": 426, "text": "terminal", "fontColor": "#E6E8F2", "fontSize": 13 },
|
||||||
|
|
||||||
|
"s5-card": { "type": "Rectangle", "left": 598, "top": 330, "width": 200, "height": 92, "fillColor": "#1E1E2E", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"s5-side": { "type": "Rectangle", "left": 606, "top": 338, "width": 22, "height": 76, "fillColor": "#181825", "strokeColor": "#181825", "corners": [3, 3, 3, 3] },
|
||||||
|
"s5-bar": { "type": "Rectangle", "left": 634, "top": 338, "width": 156, "height": 14, "fillColor": "#313244", "strokeColor": "#313244", "corners": [3, 3, 3, 3] },
|
||||||
|
"s5-accent": { "type": "Rectangle", "left": 634, "top": 360, "width": 40, "height": 4, "fillColor": "#CBA6F7", "strokeColor": "#CBA6F7" },
|
||||||
|
"s5-l1": { "type": "Rectangle", "left": 634, "top": 374, "width": 120, "height": 4, "fillColor": "#CDD6F4", "strokeColor": "#CDD6F4" },
|
||||||
|
"s5-l2": { "type": "Rectangle", "left": 634, "top": 386, "width": 90, "height": 4, "fillColor": "#7F849C", "strokeColor": "#7F849C" },
|
||||||
|
"s5-l3": { "type": "Rectangle", "left": 634, "top": 398, "width": 110, "height": 4, "fillColor": "#7F849C", "strokeColor": "#7F849C" },
|
||||||
|
"s5-name": { "type": "Text", "left": 606, "top": 426, "text": "catppuccin-mocha", "fontColor": "#E6E8F2", "fontSize": 13 },
|
||||||
|
|
||||||
|
"s6-card": { "type": "Rectangle", "left": 814, "top": 330, "width": 200, "height": 92, "fillColor": "#21262F", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"s6-side": { "type": "Rectangle", "left": 822, "top": 338, "width": 22, "height": 76, "fillColor": "#292E38", "strokeColor": "#292E38", "corners": [3, 3, 3, 3] },
|
||||||
|
"s6-bar": { "type": "Rectangle", "left": 850, "top": 338, "width": 156, "height": 14, "fillColor": "#393E48", "strokeColor": "#393E48", "corners": [3, 3, 3, 3] },
|
||||||
|
"s6-accent": { "type": "Rectangle", "left": 850, "top": 360, "width": 40, "height": 4, "fillColor": "#FA5F8B", "strokeColor": "#FA5F8B" },
|
||||||
|
"s6-l1": { "type": "Rectangle", "left": 850, "top": 374, "width": 120, "height": 4, "fillColor": "#E2E8F5", "strokeColor": "#E2E8F5" },
|
||||||
|
"s6-l2": { "type": "Rectangle", "left": 850, "top": 386, "width": 90, "height": 4, "fillColor": "#7A8296", "strokeColor": "#7A8296" },
|
||||||
|
"s6-l3": { "type": "Rectangle", "left": 850, "top": 398, "width": 110, "height": 4, "fillColor": "#7A8296", "strokeColor": "#7A8296" },
|
||||||
|
"s6-name": { "type": "Text", "left": 822, "top": 426, "text": "summer-night", "fontColor": "#E6E8F2", "fontSize": 13 },
|
||||||
|
|
||||||
|
"grp-options": { "type": "Text", "left": 382, "top": 462, "text": "OPTIONS", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
"card-options": { "type": "Rectangle", "left": 382, "top": 482, "width": 648, "height": 150, "fillColor": "#242838", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
|
||||||
|
"hc-label": { "type": "Text", "left": 398, "top": 500, "text": "High contrast", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"hc-track": { "type": "Rectangle", "left": 950, "top": 498, "width": 40, "height": 22, "fillColor": "#343850", "strokeColor": "#343850", "corners": [11, 11, 11, 11] },
|
||||||
|
"hc-knob": { "type": "Ellipse", "left": 952, "top": 500, "width": 18, "height": 18, "fillColor": "#8890AC", "strokeColor": "#8890AC" },
|
||||||
|
"hc-scope-circle": { "type": "Ellipse", "left": 1004, "top": 502, "width": 14, "height": 14, "fillColor": "#242838", "strokeColor": "#E6C370" },
|
||||||
|
"hc-scope-eq": { "type": "Rectangle", "left": 1004, "top": 508, "width": 14, "height": 1, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
"hc-scope-mer": { "type": "Rectangle", "left": 1010, "top": 502, "width": 1, "height": 14, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
|
||||||
|
"ts-label": { "type": "Text", "left": 398, "top": 544, "text": "Text size", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"ts-box": { "type": "Rectangle", "left": 926, "top": 542, "width": 64, "height": 26, "fillColor": "#20202C", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"ts-text": { "type": "Text", "left": 940, "top": 547, "text": "100%", "fontColor": "#E6E8F2", "fontSize": 14 },
|
||||||
|
"ts-scope-circle": { "type": "Ellipse", "left": 1004, "top": 546, "width": 14, "height": 14, "fillColor": "#242838", "strokeColor": "#E6C370" },
|
||||||
|
"ts-scope-eq": { "type": "Rectangle", "left": 1004, "top": 552, "width": 14, "height": 1, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
"ts-scope-mer": { "type": "Rectangle", "left": 1010, "top": 546, "width": 1, "height": 14, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
|
||||||
|
"ct-label": { "type": "Text", "left": 398, "top": 588, "text": "Custom theme", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"ct-button": { "type": "Rectangle", "left": 846, "top": 588, "width": 144, "height": 28, "fillColor": "#20202C", "strokeColor": "#3C445C", "corners": [4, 4, 4, 4] },
|
||||||
|
"ct-button-text": { "type": "Text", "left": 858, "top": 595, "text": "Import theme YAML ↗", "fontColor": "#B1BBE3", "fontSize": 13 },
|
||||||
|
"ct-scope-circle": { "type": "Ellipse", "left": 1004, "top": 592, "width": 14, "height": 14, "fillColor": "#242838", "strokeColor": "#E6C370" },
|
||||||
|
"ct-scope-eq": { "type": "Rectangle", "left": 1004, "top": 598, "width": 14, "height": 1, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
"ct-scope-mer": { "type": "Rectangle", "left": 1010, "top": 592, "width": 1, "height": 14, "fillColor": "#E6C370", "strokeColor": "#E6C370" }
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
@@ -0,0 +1,127 @@
|
|||||||
|
{
|
||||||
|
"name": "Settings — Claude",
|
||||||
|
"shapes": {
|
||||||
|
"app-bg": { "type": "Rectangle", "left": 0, "top": 0, "width": 1180, "height": 1250, "fillColor": "#17171E", "strokeColor": "#17171E" },
|
||||||
|
"app-hat": { "type": "Rectangle", "left": 0, "top": 0, "width": 1180, "height": 28, "fillColor": "#1B1B22", "strokeColor": "#1B1B22" },
|
||||||
|
"app-sidebar": { "type": "Rectangle", "left": 0, "top": 28, "width": 54, "height": 1222, "fillColor": "#1B1B22", "strokeColor": "#1B1B22" },
|
||||||
|
|
||||||
|
"modal-shadow": { "type": "Rectangle", "left": 134, "top": 66, "width": 920, "height": 1160, "fillColor": "#101015", "strokeColor": "#101015", "corners": [12, 12, 12, 12] },
|
||||||
|
"modal": { "type": "Rectangle", "left": 130, "top": 60, "width": 920, "height": 1160, "fillColor": "#20202C", "strokeColor": "#3C445C", "corners": [10, 10, 10, 10] },
|
||||||
|
|
||||||
|
"modal-title": { "type": "Text", "left": 154, "top": 76, "text": "Settings", "fontColor": "#E6E8F2", "fontSize": 18 },
|
||||||
|
"modal-close": { "type": "Text", "left": 1020, "top": 74, "text": "✕", "fontColor": "#8890AC", "fontSize": 16 },
|
||||||
|
"header-divider": { "type": "Rectangle", "left": 130, "top": 104, "width": 920, "height": 1, "fillColor": "#343850", "strokeColor": "#343850" },
|
||||||
|
|
||||||
|
"rail": { "type": "Rectangle", "left": 130, "top": 105, "width": 228, "height": 1114, "fillColor": "#1A1A24", "strokeColor": "#1A1A24", "corners": [0, 0, 0, 10] },
|
||||||
|
"rail-divider": { "type": "Rectangle", "left": 358, "top": 105, "width": 1, "height": 1114, "fillColor": "#343850", "strokeColor": "#343850" },
|
||||||
|
|
||||||
|
"search-box": { "type": "Rectangle", "left": 144, "top": 118, "width": 200, "height": 28, "fillColor": "#242838", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"search-icon": { "type": "Ellipse", "left": 154, "top": 124, "width": 11, "height": 11, "fillColor": "#242838", "strokeColor": "#78809C" },
|
||||||
|
"search-icon-handle": { "type": "Rectangle", "left": 163, "top": 133, "width": 4, "height": 1, "fillColor": "#78809C", "strokeColor": "#78809C" },
|
||||||
|
"search-text": { "type": "Text", "left": 176, "top": 124, "text": "Search all settings…", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
|
||||||
|
"cat-editor": { "type": "Text", "left": 152, "top": 164, "text": "Editor", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-keymap": { "type": "Text", "left": 152, "top": 200, "text": "Keymap", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-appearance": { "type": "Text", "left": 152, "top": 236, "text": "Appearance", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-claude-sel": { "type": "Rectangle", "left": 130, "top": 264, "width": 228, "height": 32, "fillColor": "#2C3046", "strokeColor": "#2C3046" },
|
||||||
|
"cat-claude-stripe": { "type": "Rectangle", "left": 130, "top": 264, "width": 3, "height": 32, "fillColor": "#78A0F8", "strokeColor": "#78A0F8" },
|
||||||
|
"cat-claude": { "type": "Text", "left": 152, "top": 272, "text": "Claude", "fontColor": "#E6E8F2", "fontSize": 14 },
|
||||||
|
"cat-activity": { "type": "Text", "left": 152, "top": 308, "text": "Activity", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-terminal": { "type": "Text", "left": 152, "top": 344, "text": "Terminal", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-extensions": { "type": "Text", "left": 152, "top": 380, "text": "Extensions", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
|
||||||
|
"scrollbar-track": { "type": "Rectangle", "left": 1038, "top": 110, "width": 4, "height": 1104, "fillColor": "#242838", "strokeColor": "#242838", "corners": [2, 2, 2, 2] },
|
||||||
|
"scrollbar-thumb": { "type": "Rectangle", "left": 1038, "top": 110, "width": 4, "height": 250, "fillColor": "#3C445C", "strokeColor": "#3C445C", "corners": [2, 2, 2, 2] },
|
||||||
|
|
||||||
|
"panel-title": { "type": "Text", "left": 382, "top": 120, "text": "Claude", "fontColor": "#E6E8F2", "fontSize": 18 },
|
||||||
|
"panel-sub": { "type": "Text", "left": 382, "top": 148, "text": "Model, effort, permissions, and the workspace's loaded skills, agents & MCP.", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
|
||||||
|
"grp-settings": { "type": "Text", "left": 382, "top": 186, "text": "SETTINGS", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
"card-settings": { "type": "Rectangle", "left": 382, "top": 206, "width": 648, "height": 166, "fillColor": "#242838", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"model-label": { "type": "Text", "left": 398, "top": 222, "text": "Model", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"model-select": { "type": "Rectangle", "left": 870, "top": 218, "width": 120, "height": 26, "fillColor": "#20202C", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"model-text": { "type": "Text", "left": 882, "top": 223, "text": "opus-4.8", "fontColor": "#78A0F8", "fontSize": 14 },
|
||||||
|
"model-chevron": { "type": "Text", "left": 974, "top": 223, "text": "▾", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"model-scope-circle": { "type": "Ellipse", "left": 1004, "top": 224, "width": 14, "height": 14, "fillColor": "#242838", "strokeColor": "#E6C370" },
|
||||||
|
"model-scope-eq": { "type": "Rectangle", "left": 1004, "top": 230, "width": 14, "height": 1, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
"model-scope-mer": { "type": "Rectangle", "left": 1010, "top": 224, "width": 1, "height": 14, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
|
||||||
|
"effort-label": { "type": "Text", "left": 398, "top": 254, "text": "Effort", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"effort-select": { "type": "Rectangle", "left": 870, "top": 250, "width": 120, "height": 26, "fillColor": "#20202C", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"effort-text": { "type": "Text", "left": 882, "top": 255, "text": "high", "fontColor": "#E6E8F2", "fontSize": 14 },
|
||||||
|
"effort-chevron": { "type": "Text", "left": 974, "top": 255, "text": "▾", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"effort-scope-circle": { "type": "Ellipse", "left": 1004, "top": 256, "width": 14, "height": 14, "fillColor": "#242838", "strokeColor": "#E6C370" },
|
||||||
|
"effort-scope-eq": { "type": "Rectangle", "left": 1004, "top": 262, "width": 14, "height": 1, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
"effort-scope-mer": { "type": "Rectangle", "left": 1010, "top": 256, "width": 1, "height": 14, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
|
||||||
|
"perm-label": { "type": "Text", "left": 398, "top": 286, "text": "Permission mode", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"perm-select": { "type": "Rectangle", "left": 870, "top": 282, "width": 120, "height": 26, "fillColor": "#20202C", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"perm-text": { "type": "Text", "left": 882, "top": 287, "text": "default", "fontColor": "#E6E8F2", "fontSize": 14 },
|
||||||
|
"perm-chevron": { "type": "Text", "left": 974, "top": 287, "text": "▾", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"perm-scope-body": { "type": "Rectangle", "left": 1004, "top": 290, "width": 15, "height": 10, "fillColor": "#242838", "strokeColor": "#7DD3A8", "corners": [2, 2, 2, 2] },
|
||||||
|
"perm-scope-tab": { "type": "Rectangle", "left": 1004, "top": 287, "width": 7, "height": 3, "fillColor": "#7DD3A8", "strokeColor": "#7DD3A8" },
|
||||||
|
|
||||||
|
"style-label": { "type": "Text", "left": 398, "top": 318, "text": "Output style", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"style-select": { "type": "Rectangle", "left": 870, "top": 314, "width": 120, "height": 26, "fillColor": "#20202C", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"style-text": { "type": "Text", "left": 882, "top": 319, "text": "default", "fontColor": "#E6E8F2", "fontSize": 14 },
|
||||||
|
"style-chevron": { "type": "Text", "left": 974, "top": 319, "text": "▾", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"style-scope-circle": { "type": "Ellipse", "left": 1004, "top": 320, "width": 14, "height": 14, "fillColor": "#242838", "strokeColor": "#E6C370" },
|
||||||
|
"style-scope-eq": { "type": "Rectangle", "left": 1004, "top": 326, "width": 14, "height": 1, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
"style-scope-mer": { "type": "Rectangle", "left": 1010, "top": 320, "width": 1, "height": 14, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
|
||||||
|
"source-label": { "type": "Text", "left": 398, "top": 350, "text": "Source", "fontColor": "#8890AC", "fontSize": 15 },
|
||||||
|
"source-value": { "type": "Text", "left": 870, "top": 350, "text": "~/.claude · .clide", "fontColor": "#545C84", "fontSize": 13 },
|
||||||
|
|
||||||
|
"grp-skills": { "type": "Text", "left": 382, "top": 392, "text": "SKILLS", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
"grp-skills-n": { "type": "Text", "left": 1014, "top": 392, "text": "6", "fontColor": "#545C84", "fontSize": 12 },
|
||||||
|
"card-skills": { "type": "Rectangle", "left": 382, "top": 410, "width": 648, "height": 150, "fillColor": "#242838", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"sk1": { "type": "Text", "left": 398, "top": 426, "text": "ui-design", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
"sk2": { "type": "Text", "left": 398, "top": 448, "text": "frame0-wireframe", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
"sk3": { "type": "Text", "left": 398, "top": 470, "text": "pql", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
"sk4": { "type": "Text", "left": 398, "top": 492, "text": "clide", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
"sk5": { "type": "Text", "left": 398, "top": 514, "text": "git-commit", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
"sk6": { "type": "Text", "left": 398, "top": 536, "text": "testmode", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
|
||||||
|
"grp-agents": { "type": "Text", "left": 382, "top": 580, "text": "AGENTS", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
"grp-agents-n": { "type": "Text", "left": 1020, "top": 580, "text": "3", "fontColor": "#545C84", "fontSize": 12 },
|
||||||
|
"card-agents": { "type": "Rectangle", "left": 382, "top": 598, "width": 648, "height": 84, "fillColor": "#242838", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"ag1": { "type": "Text", "left": 398, "top": 614, "text": "Explore", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
"ag2": { "type": "Text", "left": 398, "top": 636, "text": "Plan", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
"ag3": { "type": "Text", "left": 398, "top": 658, "text": "general-purpose", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
|
||||||
|
"grp-commands": { "type": "Text", "left": 382, "top": 702, "text": "COMMANDS", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
"grp-commands-n": { "type": "Text", "left": 1014, "top": 702, "text": "5", "fontColor": "#545C84", "fontSize": 12 },
|
||||||
|
"card-commands": { "type": "Rectangle", "left": 382, "top": 720, "width": 648, "height": 128, "fillColor": "#242838", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"cm1": { "type": "Text", "left": 398, "top": 736, "text": "whats-next", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
"cm2": { "type": "Text", "left": 398, "top": 758, "text": "code-review", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
"cm3": { "type": "Text", "left": 398, "top": 780, "text": "simplify", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
"cm4": { "type": "Text", "left": 398, "top": 802, "text": "verify", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
"cm5": { "type": "Text", "left": 398, "top": 824, "text": "run", "fontColor": "#78A0F8", "fontSize": 13 },
|
||||||
|
|
||||||
|
"grp-hooks": { "type": "Text", "left": 382, "top": 868, "text": "HOOKS", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
"grp-hooks-n": { "type": "Text", "left": 1020, "top": 868, "text": "3", "fontColor": "#545C84", "fontSize": 12 },
|
||||||
|
"card-hooks": { "type": "Rectangle", "left": 382, "top": 886, "width": 648, "height": 84, "fillColor": "#242838", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"hk1-ev": { "type": "Text", "left": 398, "top": 902, "text": "SessionStart", "fontColor": "#B1BBE3", "fontSize": 13 },
|
||||||
|
"hk1-cmd": { "type": "Text", "left": 540, "top": 902, "text": "peon-ping ready", "fontColor": "#8890AC", "fontSize": 12 },
|
||||||
|
"hk2-ev": { "type": "Text", "left": 398, "top": 924, "text": "UserPromptSubmit", "fontColor": "#B1BBE3", "fontSize": 13 },
|
||||||
|
"hk2-cmd": { "type": "Text", "left": 540, "top": 924, "text": "peon-ping working", "fontColor": "#8890AC", "fontSize": 12 },
|
||||||
|
"hk3-ev": { "type": "Text", "left": 398, "top": 946, "text": "Stop", "fontColor": "#B1BBE3", "fontSize": 13 },
|
||||||
|
"hk3-cmd": { "type": "Text", "left": 540, "top": 946, "text": "peon-ping done", "fontColor": "#8890AC", "fontSize": 12 },
|
||||||
|
|
||||||
|
"grp-perms": { "type": "Text", "left": 382, "top": 990, "text": "PERMISSIONS", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
"grp-perms-n": { "type": "Text", "left": 1020, "top": 990, "text": "6", "fontColor": "#545C84", "fontSize": 12 },
|
||||||
|
"card-perms": { "type": "Rectangle", "left": 382, "top": 1008, "width": 648, "height": 84, "fillColor": "#242838", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"pm-allow-k": { "type": "Text", "left": 398, "top": 1024, "text": "allow", "fontColor": "#7DD3A8", "fontSize": 12 },
|
||||||
|
"pm-allow-v": { "type": "Text", "left": 452, "top": 1024, "text": "Bash(git *) · Read · Edit · Write", "fontColor": "#B1BBE3", "fontSize": 13 },
|
||||||
|
"pm-ask-k": { "type": "Text", "left": 398, "top": 1046, "text": "ask", "fontColor": "#E6C370", "fontSize": 12 },
|
||||||
|
"pm-ask-v": { "type": "Text", "left": 452, "top": 1046, "text": "Bash(rm *)", "fontColor": "#B1BBE3", "fontSize": 13 },
|
||||||
|
"pm-deny-k": { "type": "Text", "left": 398, "top": 1068, "text": "deny", "fontColor": "#E87D7D", "fontSize": 12 },
|
||||||
|
"pm-deny-v": { "type": "Text", "left": 452, "top": 1068, "text": "Bash(curl *)", "fontColor": "#B1BBE3", "fontSize": 13 },
|
||||||
|
|
||||||
|
"grp-mcp": { "type": "Text", "left": 382, "top": 1112, "text": "MCP SERVERS", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
"grp-mcp-n": { "type": "Text", "left": 1020, "top": 1112, "text": "2", "fontColor": "#545C84", "fontSize": 12 },
|
||||||
|
"card-mcp": { "type": "Rectangle", "left": 382, "top": 1130, "width": 648, "height": 62, "fillColor": "#242838", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"mcp1": { "type": "Text", "left": 398, "top": 1146, "text": "claude.ai", "fontColor": "#E6E8F2", "fontSize": 13 },
|
||||||
|
"mcp2": { "type": "Text", "left": 398, "top": 1168, "text": "context7", "fontColor": "#E6E8F2", "fontSize": 13 }
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 167 KiB |
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"name": "Settings — Editor",
|
||||||
|
"shapes": {
|
||||||
|
"app-bg": { "type": "Rectangle", "left": 0, "top": 0, "width": 1180, "height": 720, "fillColor": "#17171E", "strokeColor": "#17171E" },
|
||||||
|
"app-hat": { "type": "Rectangle", "left": 0, "top": 0, "width": 1180, "height": 28, "fillColor": "#1B1B22", "strokeColor": "#1B1B22" },
|
||||||
|
"app-sidebar": { "type": "Rectangle", "left": 0, "top": 28, "width": 54, "height": 692, "fillColor": "#1B1B22", "strokeColor": "#1B1B22" },
|
||||||
|
|
||||||
|
"modal-shadow": { "type": "Rectangle", "left": 134, "top": 66, "width": 920, "height": 600, "fillColor": "#101015", "strokeColor": "#101015", "corners": [12, 12, 12, 12] },
|
||||||
|
"modal": { "type": "Rectangle", "left": 130, "top": 60, "width": 920, "height": 600, "fillColor": "#20202C", "strokeColor": "#3C445C", "corners": [10, 10, 10, 10] },
|
||||||
|
|
||||||
|
"modal-title": { "type": "Text", "left": 154, "top": 76, "text": "Settings", "fontColor": "#E6E8F2", "fontSize": 18 },
|
||||||
|
"modal-close": { "type": "Text", "left": 1020, "top": 74, "text": "✕", "fontColor": "#8890AC", "fontSize": 16 },
|
||||||
|
"header-divider": { "type": "Rectangle", "left": 130, "top": 104, "width": 920, "height": 1, "fillColor": "#343850", "strokeColor": "#343850" },
|
||||||
|
|
||||||
|
"rail": { "type": "Rectangle", "left": 130, "top": 105, "width": 228, "height": 554, "fillColor": "#1A1A24", "strokeColor": "#1A1A24", "corners": [0, 0, 0, 10] },
|
||||||
|
"rail-divider": { "type": "Rectangle", "left": 358, "top": 105, "width": 1, "height": 554, "fillColor": "#343850", "strokeColor": "#343850" },
|
||||||
|
|
||||||
|
"search-box": { "type": "Rectangle", "left": 144, "top": 118, "width": 200, "height": 28, "fillColor": "#242838", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"search-icon": { "type": "Ellipse", "left": 154, "top": 124, "width": 11, "height": 11, "fillColor": "#242838", "strokeColor": "#78809C" },
|
||||||
|
"search-icon-handle": { "type": "Rectangle", "left": 163, "top": 133, "width": 4, "height": 1, "fillColor": "#78809C", "strokeColor": "#78809C" },
|
||||||
|
"search-text": { "type": "Text", "left": 176, "top": 124, "text": "Search all settings…", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
|
||||||
|
"cat-editor-sel": { "type": "Rectangle", "left": 130, "top": 156, "width": 228, "height": 32, "fillColor": "#2C3046", "strokeColor": "#2C3046" },
|
||||||
|
"cat-editor-stripe": { "type": "Rectangle", "left": 130, "top": 156, "width": 3, "height": 32, "fillColor": "#78A0F8", "strokeColor": "#78A0F8" },
|
||||||
|
"cat-editor": { "type": "Text", "left": 152, "top": 164, "text": "Editor", "fontColor": "#E6E8F2", "fontSize": 14 },
|
||||||
|
"cat-keymap": { "type": "Text", "left": 152, "top": 200, "text": "Keymap", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-appearance": { "type": "Text", "left": 152, "top": 236, "text": "Appearance", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-claude": { "type": "Text", "left": 152, "top": 272, "text": "Claude", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-activity": { "type": "Text", "left": 152, "top": 308, "text": "Activity", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-terminal": { "type": "Text", "left": 152, "top": 344, "text": "Terminal", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-extensions": { "type": "Text", "left": 152, "top": 380, "text": "Extensions", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
|
||||||
|
"panel-title": { "type": "Text", "left": 382, "top": 120, "text": "Editor", "fontColor": "#E6E8F2", "fontSize": 18 },
|
||||||
|
"panel-sub": { "type": "Text", "left": 382, "top": 148, "text": "Buffer formatting, indentation, and per-folder overrides.", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
|
||||||
|
"grp-fmt": { "type": "Text", "left": 382, "top": 186, "text": "FORMATTING", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
"card-fmt": { "type": "Rectangle", "left": 382, "top": 206, "width": 648, "height": 268, "fillColor": "#242838", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
|
||||||
|
"r1-label": { "type": "Text", "left": 398, "top": 222, "text": "Format on save", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"r1-help": { "type": "Text", "left": 398, "top": 246, "text": "Run the formatter on every buffer save.", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
"r1-toggle": { "type": "Rectangle", "left": 950, "top": 224, "width": 40, "height": 22, "fillColor": "#78A0F8", "strokeColor": "#78A0F8", "corners": [11, 11, 11, 11] },
|
||||||
|
"r1-knob": { "type": "Ellipse", "left": 970, "top": 226, "width": 18, "height": 18, "fillColor": "#E6E8F2", "strokeColor": "#E6E8F2" },
|
||||||
|
"r1-scope-body": { "type": "Rectangle", "left": 1004, "top": 228, "width": 15, "height": 10, "fillColor": "#242838", "strokeColor": "#7DD3A8", "corners": [2, 2, 2, 2] },
|
||||||
|
"r1-scope-tab": { "type": "Rectangle", "left": 1004, "top": 225, "width": 7, "height": 3, "fillColor": "#7DD3A8", "strokeColor": "#7DD3A8" },
|
||||||
|
|
||||||
|
"r2-label": { "type": "Text", "left": 398, "top": 288, "text": "Indent style", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"r2-help": { "type": "Text", "left": 398, "top": 312, "text": "Spaces or tabs for new indentation.", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
"r2-select": { "type": "Rectangle", "left": 870, "top": 288, "width": 120, "height": 26, "fillColor": "#20202C", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"r2-select-text": { "type": "Text", "left": 882, "top": 293, "text": "Spaces", "fontColor": "#E6E8F2", "fontSize": 14 },
|
||||||
|
"r2-select-chevron": { "type": "Text", "left": 974, "top": 293, "text": "▾", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"r2-scope-circle": { "type": "Ellipse", "left": 1004, "top": 292, "width": 14, "height": 14, "fillColor": "#242838", "strokeColor": "#E6C370" },
|
||||||
|
"r2-scope-eq": { "type": "Rectangle", "left": 1004, "top": 298, "width": 14, "height": 1, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
"r2-scope-mer": { "type": "Rectangle", "left": 1010, "top": 292, "width": 1, "height": 14, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
|
||||||
|
"r3-label": { "type": "Text", "left": 398, "top": 354, "text": "Tab width", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"r3-help": { "type": "Text", "left": 398, "top": 378, "text": "Columns per indent level.", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
"r3-number": { "type": "Rectangle", "left": 934, "top": 354, "width": 56, "height": 26, "fillColor": "#20202C", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"r3-number-text": { "type": "Text", "left": 956, "top": 359, "text": "2", "fontColor": "#E6E8F2", "fontSize": 14 },
|
||||||
|
"r3-scope-body": { "type": "Rectangle", "left": 1004, "top": 358, "width": 15, "height": 10, "fillColor": "#242838", "strokeColor": "#7DD3A8", "corners": [2, 2, 2, 2] },
|
||||||
|
"r3-scope-tab": { "type": "Rectangle", "left": 1004, "top": 355, "width": 7, "height": 3, "fillColor": "#7DD3A8", "strokeColor": "#7DD3A8" },
|
||||||
|
|
||||||
|
"r4-label": { "type": "Text", "left": 398, "top": 420, "text": "Ruler column", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"r4-help": { "type": "Text", "left": 398, "top": 444, "text": "Wrap-guide position. Unset — inherits the default (120).", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
"r4-number": { "type": "Rectangle", "left": 934, "top": 420, "width": 56, "height": 26, "fillColor": "#20202C", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"r4-number-text": { "type": "Text", "left": 950, "top": 425, "text": "120", "fontColor": "#545C84", "fontSize": 14 },
|
||||||
|
"r4-scope-circle": { "type": "Ellipse", "left": 1004, "top": 424, "width": 14, "height": 14, "fillColor": "#242838", "strokeColor": "#78809C" },
|
||||||
|
|
||||||
|
"grp-files": { "type": "Text", "left": 382, "top": 494, "text": "FILE OVERRIDES", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
"card-files": { "type": "Rectangle", "left": 382, "top": 514, "width": 648, "height": 66, "fillColor": "#242838", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"r5-label": { "type": "Text", "left": 398, "top": 530, "text": "Project .editorconfig", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"r5-help": { "type": "Text", "left": 398, "top": 554, "text": "Per-folder overrides. Opens the file in the editor.", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
"r5-button": { "type": "Rectangle", "left": 846, "top": 530, "width": 144, "height": 28, "fillColor": "#20202C", "strokeColor": "#3C445C", "corners": [4, 4, 4, 4] },
|
||||||
|
"r5-button-text": { "type": "Text", "left": 858, "top": 537, "text": "Open .editorconfig ↗", "fontColor": "#B1BBE3", "fontSize": 13 },
|
||||||
|
"r5-scope-body": { "type": "Rectangle", "left": 1004, "top": 534, "width": 15, "height": 10, "fillColor": "#242838", "strokeColor": "#7DD3A8", "corners": [2, 2, 2, 2] },
|
||||||
|
"r5-scope-tab": { "type": "Rectangle", "left": 1004, "top": 531, "width": 7, "height": 3, "fillColor": "#7DD3A8", "strokeColor": "#7DD3A8" },
|
||||||
|
|
||||||
|
"legend-rule": { "type": "Rectangle", "left": 382, "top": 600, "width": 648, "height": 1, "fillColor": "#343850", "strokeColor": "#343850" },
|
||||||
|
"legend-title": { "type": "Text", "left": 382, "top": 616, "text": "Scope", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
"lg-proj-body": { "type": "Rectangle", "left": 438, "top": 618, "width": 15, "height": 10, "fillColor": "#20202C", "strokeColor": "#7DD3A8", "corners": [2, 2, 2, 2] },
|
||||||
|
"lg-proj-tab": { "type": "Rectangle", "left": 438, "top": 615, "width": 7, "height": 3, "fillColor": "#7DD3A8", "strokeColor": "#7DD3A8" },
|
||||||
|
"lg-proj-text": { "type": "Text", "left": 462, "top": 614, "text": "folder · Project", "fontColor": "#8890AC", "fontSize": 12 },
|
||||||
|
"lg-always-circle": { "type": "Ellipse", "left": 600, "top": 615, "width": 14, "height": 14, "fillColor": "#20202C", "strokeColor": "#E6C370" },
|
||||||
|
"lg-always-eq": { "type": "Rectangle", "left": 600, "top": 621, "width": 14, "height": 1, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
"lg-always-mer": { "type": "Rectangle", "left": 606, "top": 615, "width": 1, "height": 14, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
"lg-always-text": { "type": "Text", "left": 622, "top": 614, "text": "globe · Always", "fontColor": "#8890AC", "fontSize": 12 },
|
||||||
|
"lg-default-circle": { "type": "Ellipse", "left": 752, "top": 615, "width": 14, "height": 14, "fillColor": "#20202C", "strokeColor": "#78809C" },
|
||||||
|
"lg-default-text": { "type": "Text", "left": 774, "top": 614, "text": "circle-dashed · Default", "fontColor": "#8890AC", "fontSize": 12 }
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"name": "Settings — Search",
|
||||||
|
"shapes": {
|
||||||
|
"app-bg": { "type": "Rectangle", "left": 0, "top": 0, "width": 1180, "height": 720, "fillColor": "#17171E", "strokeColor": "#17171E" },
|
||||||
|
"app-hat": { "type": "Rectangle", "left": 0, "top": 0, "width": 1180, "height": 28, "fillColor": "#1B1B22", "strokeColor": "#1B1B22" },
|
||||||
|
"app-sidebar": { "type": "Rectangle", "left": 0, "top": 28, "width": 54, "height": 692, "fillColor": "#1B1B22", "strokeColor": "#1B1B22" },
|
||||||
|
|
||||||
|
"modal-shadow": { "type": "Rectangle", "left": 134, "top": 66, "width": 920, "height": 600, "fillColor": "#101015", "strokeColor": "#101015", "corners": [12, 12, 12, 12] },
|
||||||
|
"modal": { "type": "Rectangle", "left": 130, "top": 60, "width": 920, "height": 600, "fillColor": "#20202C", "strokeColor": "#3C445C", "corners": [10, 10, 10, 10] },
|
||||||
|
|
||||||
|
"modal-title": { "type": "Text", "left": 154, "top": 76, "text": "Settings", "fontColor": "#E6E8F2", "fontSize": 18 },
|
||||||
|
"modal-close": { "type": "Text", "left": 1020, "top": 74, "text": "✕", "fontColor": "#8890AC", "fontSize": 16 },
|
||||||
|
"header-divider": { "type": "Rectangle", "left": 130, "top": 104, "width": 920, "height": 1, "fillColor": "#343850", "strokeColor": "#343850" },
|
||||||
|
|
||||||
|
"rail": { "type": "Rectangle", "left": 130, "top": 105, "width": 228, "height": 554, "fillColor": "#1A1A24", "strokeColor": "#1A1A24", "corners": [0, 0, 0, 10] },
|
||||||
|
"rail-divider": { "type": "Rectangle", "left": 358, "top": 105, "width": 1, "height": 554, "fillColor": "#343850", "strokeColor": "#343850" },
|
||||||
|
|
||||||
|
"search-box": { "type": "Rectangle", "left": 144, "top": 118, "width": 200, "height": 28, "fillColor": "#242838", "strokeColor": "#78A0F8", "corners": [4, 4, 4, 4] },
|
||||||
|
"search-icon": { "type": "Ellipse", "left": 154, "top": 124, "width": 11, "height": 11, "fillColor": "#242838", "strokeColor": "#B1BBE3" },
|
||||||
|
"search-icon-handle": { "type": "Rectangle", "left": 163, "top": 133, "width": 4, "height": 1, "fillColor": "#B1BBE3", "strokeColor": "#B1BBE3" },
|
||||||
|
"search-text": { "type": "Text", "left": 176, "top": 124, "text": "tab", "fontColor": "#E6E8F2", "fontSize": 13 },
|
||||||
|
"search-caret": { "type": "Rectangle", "left": 198, "top": 123, "width": 1, "height": 16, "fillColor": "#78A0F8", "strokeColor": "#78A0F8" },
|
||||||
|
"search-clear": { "type": "Text", "left": 328, "top": 124, "text": "✕", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
|
||||||
|
"rail-hint": { "type": "Text", "left": 152, "top": 164, "text": "Searching all categories", "fontColor": "#545C84", "fontSize": 12 },
|
||||||
|
"cat-editor": { "type": "Text", "left": 152, "top": 196, "text": "Editor", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-editor-n": { "type": "Text", "left": 326, "top": 196, "text": "2", "fontColor": "#545C84", "fontSize": 13 },
|
||||||
|
"cat-keymap": { "type": "Text", "left": 152, "top": 228, "text": "Keymap", "fontColor": "#545C84", "fontSize": 14 },
|
||||||
|
"cat-appearance": { "type": "Text", "left": 152, "top": 260, "text": "Appearance", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"cat-appearance-n": { "type": "Text", "left": 326, "top": 260, "text": "1", "fontColor": "#545C84", "fontSize": 13 },
|
||||||
|
"cat-claude": { "type": "Text", "left": 152, "top": 292, "text": "Claude", "fontColor": "#545C84", "fontSize": 14 },
|
||||||
|
"cat-activity": { "type": "Text", "left": 152, "top": 324, "text": "Activity", "fontColor": "#545C84", "fontSize": 14 },
|
||||||
|
"cat-terminal": { "type": "Text", "left": 152, "top": 356, "text": "Terminal", "fontColor": "#545C84", "fontSize": 14 },
|
||||||
|
"cat-extensions": { "type": "Text", "left": 152, "top": 388, "text": "Extensions", "fontColor": "#545C84", "fontSize": 14 },
|
||||||
|
|
||||||
|
"results-head": { "type": "Text", "left": 382, "top": 120, "text": "3 settings match “tab”", "fontColor": "#B1BBE3", "fontSize": 14 },
|
||||||
|
|
||||||
|
"grp-editor": { "type": "Text", "left": 382, "top": 160, "text": "EDITOR", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
"card-editor": { "type": "Rectangle", "left": 382, "top": 178, "width": 648, "height": 132, "fillColor": "#242838", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
|
||||||
|
"r1-label": { "type": "Text", "left": 398, "top": 194, "text": "Tab width", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"r1-help": { "type": "Text", "left": 398, "top": 218, "text": "Columns per indent level.", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
"r1-number": { "type": "Rectangle", "left": 934, "top": 194, "width": 56, "height": 26, "fillColor": "#20202C", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"r1-number-text": { "type": "Text", "left": 956, "top": 199, "text": "2", "fontColor": "#E6E8F2", "fontSize": 14 },
|
||||||
|
"r1-scope-body": { "type": "Rectangle", "left": 1004, "top": 198, "width": 15, "height": 10, "fillColor": "#242838", "strokeColor": "#7DD3A8", "corners": [2, 2, 2, 2] },
|
||||||
|
"r1-scope-tab": { "type": "Rectangle", "left": 1004, "top": 195, "width": 7, "height": 3, "fillColor": "#7DD3A8", "strokeColor": "#7DD3A8" },
|
||||||
|
|
||||||
|
"r2-label": { "type": "Text", "left": 398, "top": 260, "text": "Indent style", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"r2-help": { "type": "Text", "left": 398, "top": 284, "text": "Spaces or tabs for new indentation.", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
"r2-select": { "type": "Rectangle", "left": 870, "top": 260, "width": 120, "height": 26, "fillColor": "#20202C", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"r2-select-text": { "type": "Text", "left": 882, "top": 265, "text": "Spaces", "fontColor": "#E6E8F2", "fontSize": 14 },
|
||||||
|
"r2-select-chevron": { "type": "Text", "left": 974, "top": 265, "text": "▾", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"r2-scope-circle": { "type": "Ellipse", "left": 1004, "top": 264, "width": 14, "height": 14, "fillColor": "#242838", "strokeColor": "#E6C370" },
|
||||||
|
"r2-scope-eq": { "type": "Rectangle", "left": 1004, "top": 270, "width": 14, "height": 1, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
"r2-scope-mer": { "type": "Rectangle", "left": 1010, "top": 264, "width": 1, "height": 14, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
|
||||||
|
"grp-appearance": { "type": "Text", "left": 382, "top": 330, "text": "APPEARANCE", "fontColor": "#78809C", "fontSize": 12 },
|
||||||
|
"card-appearance": { "type": "Rectangle", "left": 382, "top": 348, "width": 648, "height": 66, "fillColor": "#242838", "strokeColor": "#343850", "corners": [6, 6, 6, 6] },
|
||||||
|
"r3-label": { "type": "Text", "left": 398, "top": 364, "text": "Workspace tab density", "fontColor": "#E6E8F2", "fontSize": 15 },
|
||||||
|
"r3-help": { "type": "Text", "left": 398, "top": 388, "text": "Spacing of the workspace tab strip.", "fontColor": "#78809C", "fontSize": 13 },
|
||||||
|
"r3-select": { "type": "Rectangle", "left": 870, "top": 364, "width": 120, "height": 26, "fillColor": "#20202C", "strokeColor": "#343850", "corners": [4, 4, 4, 4] },
|
||||||
|
"r3-select-text": { "type": "Text", "left": 882, "top": 369, "text": "Compact", "fontColor": "#E6E8F2", "fontSize": 14 },
|
||||||
|
"r3-select-chevron": { "type": "Text", "left": 974, "top": 369, "text": "▾", "fontColor": "#8890AC", "fontSize": 14 },
|
||||||
|
"r3-scope-circle": { "type": "Ellipse", "left": 1004, "top": 368, "width": 14, "height": 14, "fillColor": "#242838", "strokeColor": "#E6C370" },
|
||||||
|
"r3-scope-eq": { "type": "Rectangle", "left": 1004, "top": 374, "width": 14, "height": 1, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
"r3-scope-mer": { "type": "Rectangle", "left": 1010, "top": 368, "width": 1, "height": 14, "fillColor": "#E6C370", "strokeColor": "#E6C370" },
|
||||||
|
|
||||||
|
"footer-note": { "type": "Text", "left": 382, "top": 444, "text": "Results span every category — edit a field inline, or open its category for the full list.", "fontColor": "#545C84", "fontSize": 12 }
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
+428
@@ -0,0 +1,428 @@
|
|||||||
|
# fable-ous.md
|
||||||
|
|
||||||
|
*A fable about clide, as told by Fable.*
|
||||||
|
|
||||||
|
*Produced by 13 parallel subsystem reviewers reading ~58k LOC of Dart, ~100 raw
|
||||||
|
findings put through 70 adversarial verification passes (exactly one finding was
|
||||||
|
refuted — it had missed D-14), 5 feature-ideation lenses, and a full read of the
|
||||||
|
pql planning vault: 94 confirmed decisions, 24 open questions, 11 rejected
|
||||||
|
alternatives, 358 tickets. Everything below cites real file:line evidence that a
|
||||||
|
skeptical second agent re-read and failed to knock down.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR scoreboard
|
||||||
|
|
||||||
|
| Dimension | Verdict |
|
||||||
|
|---|---|
|
||||||
|
| Build health | **Green.** `make analyze` 0 issues, 1383 tests pass, format clean, coverage 95.30% over the 95 floor |
|
||||||
|
| Documentation discipline | **Best-in-class.** Near-every file cites its D-NNN/T-NNN; zero TODO/FIXME debt in 58k LOC |
|
||||||
|
| Honesty | **High.** CHANGELOG claims verified against code; stubs self-describe as stubs |
|
||||||
|
| Real bugs found | ~14 distinct high-severity, ~35 medium — mostly in process lifecycle, a11y, and terminal conformance |
|
||||||
|
| Systemic risks | Broadcast-streams-without-replay, silent `catch (_)`, kernel lookup in `dispose()`, copy-paste drift |
|
||||||
|
| Release process | **Stalled.** No git tag since v2.1.0 despite five CHANGELOG releases; `ci/release.sh` is a stub |
|
||||||
|
| Killer-feature headroom | Enormous — the moats (owned agent loop, owned renderer, pql vault) are real and barely exploited |
|
||||||
|
|
||||||
|
The fable in one sentence: **a castle with exceptional masonry, a few unlocked
|
||||||
|
side doors, and a dragon hoard of features in the basement nobody has spent yet.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part I — The state of the realm (what's genuinely excellent)
|
||||||
|
|
||||||
|
Credit where due, because this codebase does several things better than most
|
||||||
|
production repos:
|
||||||
|
|
||||||
|
- **Governance traceability is real, not ceremonial.** Nearly every non-trivial
|
||||||
|
declaration carries its ticket/decision id. Multiple reviewers independently
|
||||||
|
called it "the best I've seen at this scale." Code-to-decision drift is
|
||||||
|
*auditable from the code itself* — and indeed most drift findings below were
|
||||||
|
found exactly that way.
|
||||||
|
- **Schema-validated IPC dispatch (D-74)** is beautifully executed: schemas
|
||||||
|
co-registered with handlers, the MCP tool surface and `clide capabilities`
|
||||||
|
both generated from the same registry (`lib/src/daemon/dispatcher.dart:90-137`)
|
||||||
|
so three surfaces cannot drift apart.
|
||||||
|
- **The keymap subsystem (D-82)** — layered precedence, headless
|
||||||
|
`SequenceMatcher`, clock-injected `ModifierTapTracker` — is exemplary,
|
||||||
|
near-fully test-mirrored design.
|
||||||
|
- **`ClideTappable`, the anchored-overlay/menu family (D-88), reduced-motion
|
||||||
|
honoring in every animated primitive** — the owned widget layer is coherent
|
||||||
|
and disciplined.
|
||||||
|
- **Battle scars are encoded where they happened.** `pumpAsync` documents why
|
||||||
|
`pumpAndSettle` wedges; `KernelFixture` encodes the T-280 teardown-hang fix;
|
||||||
|
flake postmortems live as comments in the test that almost shipped them.
|
||||||
|
- **Security instincts**: 0600 socket + live-listener probe before unlink, git
|
||||||
|
argv hardening with `--` terminators, `path_safety.dart` documenting its
|
||||||
|
threat models inline, workspace-relative binary resolution forbidden (T-98).
|
||||||
|
- **Zero TODO/FIXME/HACK** across the tree. The clean-board policy is lived.
|
||||||
|
|
||||||
|
Patterns worth *extending* (the reviewers kept wishing other code did this):
|
||||||
|
the coalesced-notify `Timer(Duration.zero)` trick in `ConversationController`,
|
||||||
|
the pure-Dart-core/thin-Flutter-shell split, and `RecordingEventSink`-style
|
||||||
|
event-driven test waits.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part II — The bestiary (bugs I would fix, in order)
|
||||||
|
|
||||||
|
### 🐉 Dragons (high severity, verified, fix this week)
|
||||||
|
|
||||||
|
1. **Every naturally-exited PTY leaks its master fd — forever.**
|
||||||
|
`lib/src/pty/native_pty.dart:444-450` — on child EOF, `_reap()` sets
|
||||||
|
`_dead = true` but never closes `_fd`; a later `close()` short-circuits at
|
||||||
|
`if (_dead) return;` (line 460) so `_nativeClose(_fd)` (line 477) never runs.
|
||||||
|
Two reviewers found this independently. Every terminal/Claude pane whose
|
||||||
|
child exits on its own leaks an fd and a pty device for the life of the app.
|
||||||
|
|
||||||
|
2. **The Claude child process is observed only via stdout.** Three reviewers
|
||||||
|
converged here. `lib/builtin/claude/src/stream_json_session.dart:43-78` —
|
||||||
|
stderr is *never drained* (≥64KB of `--verbose` spew = pipe fills = child
|
||||||
|
blocks mid-turn = the flagship pane wedges with zero diagnostics), and
|
||||||
|
nothing watches `exitCode` or `onDone` (line 303), so a crashed/dead
|
||||||
|
session just looks… thoughtful. Drain stderr into a ring buffer, surface a
|
||||||
|
terminal `SessionEnded` state. This also intersects T-283 (no resume
|
||||||
|
timeout/fallback).
|
||||||
|
|
||||||
|
3. **The MCP HTTP server exposes the entire dispatcher with zero auth.**
|
||||||
|
`lib/src/ipc/mcp_server.dart:138-195`, started unconditionally at boot
|
||||||
|
(`lib/main.dart:174-180`). D-71's threat model ("another user on the same
|
||||||
|
host should not drive my IDE") is enforced with 0600 on the unix socket —
|
||||||
|
and then bypassed wholesale by an unauthenticated localhost HTTP port that,
|
||||||
|
since D-86, serves *every* clide verb as a tool. Generate a token in the
|
||||||
|
lock file (Claude Code's own `/ide` lock format has a slot for it), require
|
||||||
|
the header.
|
||||||
|
|
||||||
|
4. **`editor.open`/`editor.save` skip path confinement entirely.**
|
||||||
|
`lib/src/editor/registry.dart:215-219` returns absolute paths verbatim, no
|
||||||
|
`..` normalization, no `path_safety` call — an unconfined read *and write*
|
||||||
|
primitive over IPC while `files.read` is carefully guarded. Same family:
|
||||||
|
**`search.replace` silently ignores its include/exclude globs**
|
||||||
|
(`lib/src/search/replace_engine.dart:124-143`) and will happily rewrite
|
||||||
|
files outside the filter the user typed; and **`listDir`'s symlink
|
||||||
|
detection is dead code** (`lib/src/files/listing.dart:46-54` — `stat()`
|
||||||
|
follows links, so `isSymlink` is always false) which means `walkFiles`
|
||||||
|
descends symlinked dirs the docs claim it skips.
|
||||||
|
|
||||||
|
5. **Closing a terminal pane never closes the shell.**
|
||||||
|
`lib/builtin/terminal/src/terminal_pane.dart:131-137` calls
|
||||||
|
`ClideKernel.of(context)` from `dispose()` — illegal ancestor lookup,
|
||||||
|
swallowed by `catch (_)` — so `pane.close` is never sent and the backend
|
||||||
|
PTY + daemon pane leak. The same idiom leaks the settings listener in every
|
||||||
|
disposed `ClaudePane` (`claude_pane.dart:460-466`). Combined with dragon #1
|
||||||
|
this is a two-stage leak pipeline. Cache the kernel ref in
|
||||||
|
`didChangeDependencies`, delete the catch-all.
|
||||||
|
|
||||||
|
6. **Project switch leaks the entire previous workspace.**
|
||||||
|
`lib/main.dart:335-344` — a new dispatcher gets fresh `PaneRegistry`,
|
||||||
|
`FilesService`, `EditorRegistry`, etc., but nothing calls the old set's
|
||||||
|
`shutdown()` methods (which exist and have zero callers). Old watchers keep
|
||||||
|
emitting into the new workspace's bus.
|
||||||
|
|
||||||
|
7. **T-274's root cause, found and verified:** the status bar is empty because
|
||||||
|
`statusStream` is a plain broadcast controller — the `system/init` event
|
||||||
|
fires while `spawn()` is still awaiting a 256KB transcript-tail read, before
|
||||||
|
the pane ever subscribes (`claude_pane.dart:322`,
|
||||||
|
`session_orchestrator.dart:222-225`). Seed from `session.status` on bind, or
|
||||||
|
make it replay-latest. (The broadcast-without-replay shape is a recurring
|
||||||
|
bug factory — see Part III.)
|
||||||
|
|
||||||
|
8. **Auto-scroll yanks a scrolled-up reader to the bottom on every streamed
|
||||||
|
token.** `conversation_view.dart:268-277` — the `_atBottom` pin exists but
|
||||||
|
is only consulted on viewport *resize*, not on new items. Anyone reading
|
||||||
|
earlier output during a long streaming reply is dragged to the bottom
|
||||||
|
continuously. One-line gate + the missing twin test.
|
||||||
|
|
||||||
|
9. **The terminal can crash on garbled output.** SGR 38/48 extended-color
|
||||||
|
parsing does unguarded `params[i + 1]` lookahead
|
||||||
|
(`lib/src/terminal/src/core/escape/parser.dart:501-516`) — `printf '\e[38m'`
|
||||||
|
throws RangeError inside `Terminal.write`. An emulator must never throw on
|
||||||
|
hostile bytes. While in there: colon-form SGR sub-parameters are mangled
|
||||||
|
into bogus params.
|
||||||
|
|
||||||
|
10. **A11y has drifted despite being a Tier-0 contract.** Two independent
|
||||||
|
verified findings: `ClideCollapserCard`'s `excludeSemantics: true` wipes
|
||||||
|
*every expanded child* from the a11y tree
|
||||||
|
(`lib/widgets/src/clide_collapser_card.dart:92-101`) — a screen-reader user
|
||||||
|
can expand a run and hear nothing; and the three a11y gate tests
|
||||||
|
hand-enumerate their subjects and have measurably fallen behind `lib/`
|
||||||
|
(contrast checks fewer themes than `main.dart:443-454` loads; i18n checks 4
|
||||||
|
of 8 namespaces). The gates stay green while covering less. Make `lib/`
|
||||||
|
export the canonical lists and iterate them in the gates.
|
||||||
|
|
||||||
|
### 🦂 Scorpions (medium — real, will sting eventually)
|
||||||
|
|
||||||
|
- **D-72's "serial dispatch" isn't.** `lib/src/ipc/server.dart:151-180` uses an
|
||||||
|
`async` onData without pausing the subscription — pipelined requests
|
||||||
|
interleave, and the shared `StringBuffer` framing can drop/double lines.
|
||||||
|
`client.cast<List<int>>().transform(utf8.decoder).transform(LineSplitter())`
|
||||||
|
+ `await for` fixes framing, UTF-8 split chunks, and serialization at once.
|
||||||
|
- **Split-chunk UTF-8 corruption is endemic at byte→String seams**: the
|
||||||
|
terminal's only ingestion API is `write(String)` (`terminal.dart:218`) so
|
||||||
|
both consumers decode per-chunk; `FileTailFollower` starts mid-character by
|
||||||
|
construction. Add `writeBytes()` with a persistent chunked decoder.
|
||||||
|
- **`Orchestrator.spawn()` races itself** — check-then-act across two awaits;
|
||||||
|
concurrent spawns for one id leak a live `claude` process
|
||||||
|
(`session_orchestrator.dart:191-249`). Hold a `Map<String, Future<ManagedSession>>`.
|
||||||
|
- **Fork panes misbehave on `/clear`, `/resume`, `/fork`** —
|
||||||
|
`widget.forkSourceId` wins forever, so `/clear` *re-forks the original
|
||||||
|
conversation* instead of clearing (`claude_pane.dart:266-281`).
|
||||||
|
- **Settings persistence corrupts maps-inside-lists on write**
|
||||||
|
(`settings.dart:199-219` emits `toString()`), breaking the documented keymap
|
||||||
|
overlay across restarts; writes are non-atomic and a parse failure silently
|
||||||
|
resets all settings.
|
||||||
|
- **Extension activation isn't transactional** — a throw mid-contribution
|
||||||
|
leaves contributions mounted while the extension records as failed; retry
|
||||||
|
double-applies (`extensions_manager.dart:133-192`). Plus: disabling an
|
||||||
|
extension ignores dependents, and registries clobber silently on id
|
||||||
|
collision — fine among curated builtins, hazardous the day Tier-6 Lua lands.
|
||||||
|
- **Terminal conformance debt** (the fork fixes what it trips over but has no
|
||||||
|
vttest-style suite): HTS is a no-op (`isSetAt` instead of `setAt`,
|
||||||
|
`terminal.dart:423`), DECCKM is tracked but never consumed, legacy mouse rows
|
||||||
|
are off-by-one *and the test enshrines the bug*, CPR replies 0-based where
|
||||||
|
every real terminal is 1-based, scrollback is maintained but structurally
|
||||||
|
unreachable (`ViewportOffset.zero()` pinned every build,
|
||||||
|
`terminal_view.dart:224`).
|
||||||
|
- **Markdown renderer**: hard breaks and images render as empty text (words
|
||||||
|
glue together; `clide_markdown.dart:408-410`), and the whole document
|
||||||
|
re-parses with sync `existsSync()` calls *inside build* on every streaming
|
||||||
|
delta — multiplied by the conversation view re-deriving everything O(n) per
|
||||||
|
notification and token streaming re-encoding the full reply per delta
|
||||||
|
(O(n²) churn, `stream_json_session.dart:410-431`).
|
||||||
|
- **Terminal panes spawn in `Directory.current`**, not the open project root
|
||||||
|
(`terminal_pane.dart:69`) — desktop launches get `$HOME` shells.
|
||||||
|
- **The Notifications service renders nowhere** — `notify.dart` has zero widget
|
||||||
|
consumers; cli_install's dogfood warnings vanish into an unrendered list
|
||||||
|
while `ToastService` sits right there.
|
||||||
|
- **Welcome screen no-ops**: "Clone from git…" and "Start a Claude session"
|
||||||
|
advertise shortcuts that don't exist and do nothing on tap
|
||||||
|
(`welcome_view.dart:173-174`).
|
||||||
|
- **`make test-e2e`/`ui-dev`/`ui-smoke` are dead** — `tools/ui/*.sh` still `cd`
|
||||||
|
into the removed `app/` directory; the staged Gitea CI workflow would fail in
|
||||||
|
three independent ways on activation, while D-32 calls it "ready."
|
||||||
|
|
||||||
|
### 🐀 Rats (low, but they breed)
|
||||||
|
|
||||||
|
Dead code worth a one-day extermination sweep: the entire legacy free-function
|
||||||
|
git API (~250 LOC duplicating `GitClient`, kept alive only by tests, *with its
|
||||||
|
own latent pipe-deadlock bug*), `ToolCheck`, ~60% of `ffi/libc.dart` (fd-passing
|
||||||
|
era), `GraphView` (unreachable placeholder), `ColumnHat` (duplicated
|
||||||
|
line-for-line in app.dart, kept alive by a zero-coverage test), the tmux-era
|
||||||
|
team pipeline (`TranscriptPublisher`, `TeamMemberJoined` — *nothing emits these
|
||||||
|
events*, yet the team roster UI still listens to them exclusively, meaning team
|
||||||
|
tiles are populated by ghosts), the dead `ptyc` binary still committed in
|
||||||
|
`native/linux-x64/` against D-62/D-63, and `mocktail` — pinned, documented in
|
||||||
|
D-25 as the IO-mocking strategy, and imported by exactly zero files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part III — Patterns I would change (the systemic stuff)
|
||||||
|
|
||||||
|
1. **Broadcast streams that carry state need replay-latest.** This one shape
|
||||||
|
caused T-274, the meta sidebar's manual compensation, and the
|
||||||
|
prompt-stream's `initialData` workaround. Write a tiny `ValueStream` wrapper
|
||||||
|
once; retrofit `statusStream`, `busyStream`, `pendingPromptStream`.
|
||||||
|
|
||||||
|
2. **Ban `catch (_) {}` on I/O and lifecycle paths.** The silent-swallow idiom
|
||||||
|
turned an illegal-lookup-in-dispose into two resource leaks and turned
|
||||||
|
process-spawn failures into blank panes. Cleanup paths may swallow; spawn,
|
||||||
|
read, and dispose paths must log through the kernel Logger they already have.
|
||||||
|
|
||||||
|
3. **Sync I/O in async handlers on the single isolate.** `files.read` does a
|
||||||
|
sync 10MB read; the replace engine reads and rewrites the workspace
|
||||||
|
synchronously *while grep right next to it fans out to isolates per D-79*.
|
||||||
|
Decide the rule (offload above N KB), write it into a D-record, apply it.
|
||||||
|
|
||||||
|
4. **Path confinement belongs at the dispatch layer, not per-verb.** files.read
|
||||||
|
remembered, search.replace half-remembered, editor.* forgot. A confinement
|
||||||
|
check keyed off the co-registered schema (the registry already knows which
|
||||||
|
params are paths) ends the per-verb lottery.
|
||||||
|
|
||||||
|
5. **Copy-paste is the repo's main duplication tax.** The welcome screen clones
|
||||||
|
FileActions' entire open-folder flow verbatim; palette and quick-open are
|
||||||
|
~230-line near-twins; three private "tail a growing file" implementations in
|
||||||
|
the claude builtin alone; five hand-rolled `_userErr` helpers; five
|
||||||
|
copy-pasted git test sandboxes (none isolating host git config); two
|
||||||
|
parallel ANSI flag enums that already drifted (strikethrough is stored but
|
||||||
|
never painted). Each is small; together they're how a solo-dev repo rots.
|
||||||
|
|
||||||
|
6. **Hand-enumerated lists drift; export the truth.** Bundled themes (already
|
||||||
|
drifted between `main.dart` and the testmode harness — catppuccin is
|
||||||
|
silently unvalidated), a11y gate subjects, i18n namespaces. One exported
|
||||||
|
const each, consumed by both sides.
|
||||||
|
|
||||||
|
7. **The claude builtin returns `ok` with an `error` payload in 16 handlers**,
|
||||||
|
drifting from the D-6 exit-code contract every other subsystem honors. A
|
||||||
|
scripted `clide claude.agent.set-permission-mode bogus` exits 0 today.
|
||||||
|
|
||||||
|
8. **God-files**: `app.dart` (1187 LOC, five concerns — split plan is in the
|
||||||
|
findings), `claude_meta_sidebar.dart` (1192), `parser.dart` (1139, T-123
|
||||||
|
already exists — and the split should also fix `_consumeCsi` discarding
|
||||||
|
intermediate bytes, which permanently blocks DECSCUSR/DECSTR).
|
||||||
|
|
||||||
|
9. **Docs drift at the front door**: CLAUDE.md and README still say "tmux owns
|
||||||
|
Claude session persistence (D-41)" — superseded by D-75/D-77 per
|
||||||
|
`docs/architecture.md`; README says "Pre-v2.0 (2.0.0-dev)" at v2.3.3 and
|
||||||
|
headlines "canvas and graph surfaces" that are a 17-line stub and a flat
|
||||||
|
ListView respectively. clide's honesty is its brand; the README is the one
|
||||||
|
place currently off-brand.
|
||||||
|
|
||||||
|
10. **Close the release loop.** Five CHANGELOG releases since the last git tag;
|
||||||
|
`ci/release.sh` exits 64 and references the dissolved sidecar; the pre-push
|
||||||
|
fast path's safety argument cites "release CI on tagged versions" that
|
||||||
|
doesn't exist; and the fast path skips ALL tests for pushes touching
|
||||||
|
`test/`, `ci/`, or the hook itself. Back-tag 2.2.0–2.3.3, add tagging to
|
||||||
|
the git-commit skill ritual, widen the fast-path regex. (This is also the
|
||||||
|
blocking prerequisite your own T-47 refinement identified for self-update.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part IV — Killer features (the dragon hoard)
|
||||||
|
|
||||||
|
Five ideation lenses, 27 proposals, deduplicated and ranked. The convergence
|
||||||
|
test mattered: **two lenses independently invented the flight recorder, and two
|
||||||
|
independently invented the visual canvas round-trip** — when separate agents
|
||||||
|
with different briefs land on the same feature, that's the market talking.
|
||||||
|
|
||||||
|
Clide's structural moats, verified against code: it *spawns and owns* the agent
|
||||||
|
process (D-77/D-78) where competitors are sandboxed extension guests; it owns
|
||||||
|
every pixel (terminal, markdown, canvas); everything is local-and-committed
|
||||||
|
(transcripts, costs, decisions, tickets) where competitors' business models
|
||||||
|
require cloud custody; and the pql vault is structured planning data no
|
||||||
|
mainstream IDE has an analogue for.
|
||||||
|
|
||||||
|
### Tier 1 — do these (high leverage, mostly M-effort, plumbing exists)
|
||||||
|
|
||||||
|
1. **Agent Blame + Session Flight Recorder** `[L]` — gutter action on any line:
|
||||||
|
*which session, which turn, which prompt, which permission grant, what it
|
||||||
|
cost* — opening the native conversation at the exact `tool_use`. Timeline
|
||||||
|
scrubber to replay a session. The transcript pipeline
|
||||||
|
(`transcript_reader.dart`, `session_index.dart`) already parses everything
|
||||||
|
needed. Cursor/Copilot cannot ship this: their logs live server-side by
|
||||||
|
business design. *Two lenses converged here.*
|
||||||
|
|
||||||
|
2. **Context X-ray** `[M]` — per-card token attribution ("this 40KB Bash tail
|
||||||
|
is 12% of your window") + a real compaction indicator. `stream_json_session.dart`
|
||||||
|
already parses usage and contextWindow per event; the renderer owns the
|
||||||
|
cards. Fixes T-244 (invisible compaction) as a side effect. Context is the
|
||||||
|
scarcest resource in agent pairing and every tool renders it as one opaque
|
||||||
|
percentage.
|
||||||
|
|
||||||
|
3. **Trust Ledger + decision-aware permission prompts** `[M]` — every
|
||||||
|
permission rule with provenance (which prompt, which session, which ticket),
|
||||||
|
ticket-scoped expiry; and when a `can_use_tool` request arrives, chip the
|
||||||
|
relevant D-record onto the card (edit touching pubspec.yaml → D-31
|
||||||
|
prefer-zero-deps, one keystroke to deny *with the decision cited*).
|
||||||
|
Governance stops being documentation and becomes live agent policy. No
|
||||||
|
competitor has a queryable in-repo decision system to even attempt this.
|
||||||
|
|
||||||
|
4. **Agent Activity HUD** `[M]` — four backlog tickets and one open question
|
||||||
|
are secretly one feature: build T-59's `OperationsRegistry` once and feed it
|
||||||
|
git/pql progress (T-59), sidebar badges (T-58), compaction state (T-244),
|
||||||
|
the status strip (T-274), with Q-34's budget slot reserved. Fix the T-274
|
||||||
|
plumbing bug first or the HUD inherits blank-slot syndrome.
|
||||||
|
|
||||||
|
5. **Active Ticket Context** `[S]` ← *cheapest win in the whole list* — picking
|
||||||
|
up a ticket binds it to the session: a "working on T-244" chip, auto
|
||||||
|
`in_progress` flip, `(T-NNN)` pre-suggested in commit messages, changelog
|
||||||
|
reminder on done. Makes kanban ambient instead of homework, and makes every
|
||||||
|
trail/ledger feature below reliable.
|
||||||
|
|
||||||
|
### Tier 2 — the differentiators (L/XL, each could headline a release)
|
||||||
|
|
||||||
|
6. **Twin-timeline rewind** `[L]` — snapshot the worktree as hidden git refs
|
||||||
|
(`git write-tree` → `refs/clide/checkpoints`) at every turn boundary, keyed
|
||||||
|
to turn uuid; every user-message card gains "restore files to before this."
|
||||||
|
Claude's `/rewind` only restores what Claude itself edited; clide owns both
|
||||||
|
timelines.
|
||||||
|
|
||||||
|
7. **Visual Dialog** `[L]` — one bidirectional scene schema: Claude draws
|
||||||
|
(D-91 canvas cards), the user annotates in the interaction zone (T-260), and
|
||||||
|
the annotations return as *structured geometry + flattened PNG*, not prose.
|
||||||
|
Merge the T-317/T-318 and T-260 specs into one protocol before they become
|
||||||
|
two dialects. *Two lenses converged here.*
|
||||||
|
|
||||||
|
8. **Immortal terminals** `[M]` — T-258 (terminal as editor-mode peer) fused
|
||||||
|
with T-325 live-tails: any long process — user shell *or* agent-spawned
|
||||||
|
build — promotes to a full tmux-backed surface that survives restart.
|
||||||
|
"The build that never dies" is structural for clide, a plugin fantasy for
|
||||||
|
Electron. Resolve Q-27 (swap vs split) as part of it, as T-258 already notes.
|
||||||
|
|
||||||
|
9. **Local cost ledger** `[M]` — per-turn cost/tokens persisted against the
|
||||||
|
active pql ticket; the board shows what each feature actually cost.
|
||||||
|
Flat-subscription opacity is the competitors' business model; turn-level
|
||||||
|
local cost data is clide's birthright. (Tokens primary, dollars advisory —
|
||||||
|
subscription auth reports notional costs.)
|
||||||
|
|
||||||
|
10. **Label-routed work queues / ticket dispatch** `[M→XL]` — T-277 labels +
|
||||||
|
the shipped pick-up path turn the board into an agent control surface;
|
||||||
|
the XL extension dispatches a ticket to a teammate session in an isolated
|
||||||
|
git worktree (SpawnSpec.cwd already exists). Local, no-telemetry
|
||||||
|
background agents with the work item, isolation, review surface, and audit
|
||||||
|
trail all in-repo.
|
||||||
|
|
||||||
|
### Tier 3 — moonshots (XL, pick one per quarter, they compound)
|
||||||
|
|
||||||
|
11. **Semantic terminal** — OSC 133 markers (clide spawns the shell, injection
|
||||||
|
is trivial) lift scrollback into foldable command regions with recognizers
|
||||||
|
for test runners and stack traces; real widgets between rows is something
|
||||||
|
xterm.js structurally cannot do. *Note: fix the scrollback-unreachable bug
|
||||||
|
first — a semantic scrollback you can't scroll is a koan.*
|
||||||
|
12. **Living codebase map** — tree-sitter imports + pql links + git churn on
|
||||||
|
the owned canvas, with live agent heat from `tool_use` events: watch Claude
|
||||||
|
*move through your codebase* in real time.
|
||||||
|
13. **Live mixed documents** — fenced blocks in the owned markdown renderer
|
||||||
|
become live embeds (canvas scenes, pql query results, decision cards,
|
||||||
|
confirm-gated command buttons). Notebook-grade, zero webview. The
|
||||||
|
`ClideMarkdownHooks` seam already exists.
|
||||||
|
14. **Remote Claude over SSH** — T-329 is fully ticketed and undersold: agent
|
||||||
|
on the buildbox, permission prompts rendering natively local. VS Code
|
||||||
|
Remote moves the editor; nobody remotes the *agent control channel*.
|
||||||
|
15. **Sealed-workspace mode** — an egress-audit proxy around the whole agent
|
||||||
|
stack, operationalizing D-60/D-64 into a provable property. The one
|
||||||
|
feature in this list competitors *cannot* copy without breaking their own
|
||||||
|
products.
|
||||||
|
|
||||||
|
### Honorable mentions
|
||||||
|
|
||||||
|
Plan-to-Board bridge (ExitPlanMode approval files the plan as a ticket tree),
|
||||||
|
Release Cockpit (renders `[Unreleased]` with word-count badges + one-action
|
||||||
|
release cut — would also unstall Part III #10), Daily Helm ("since you were
|
||||||
|
last here" pulse on the welcome screen, computed from data the repo already
|
||||||
|
commits), Total-recall conversation search (D-79 grep over the transcript
|
||||||
|
corpus + fork-from-here), Shared Gaze (attach editor selection/scroll context
|
||||||
|
to each outgoing turn), Speakable Layouts (`clide layout apply review.yaml` —
|
||||||
|
the agent stages your workspace), Agent fleet tray (enumerate per-workspace
|
||||||
|
sockets, show every repo's agent state — *requires fixing T-247's stale-socket
|
||||||
|
litter first*), Governance Graph (the D/Q/R/T web as a navigable map — gives
|
||||||
|
the placeholder graph view a flagship dataset).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part V — If I were you, Monday morning
|
||||||
|
|
||||||
|
1. **One leak-fix commit**: PTY fd on natural exit + kernel-lookup-in-dispose
|
||||||
|
(terminal & claude panes) + project-switch service disposal. Three findings,
|
||||||
|
one theme, one afternoon.
|
||||||
|
2. **One Claude-resilience commit**: drain stderr, watch exitCode, seed status
|
||||||
|
on bind, gate auto-scroll on `_atBottom`. The flagship pane stops having
|
||||||
|
silent failure modes.
|
||||||
|
3. **One security commit**: MCP auth token + editor.* path confinement +
|
||||||
|
search.replace glob filter + symlink-walk fix.
|
||||||
|
4. **One rat-extermination day**: dead git API, ToolCheck, libc bindings,
|
||||||
|
ColumnHat, GraphView, tmux-era team pipeline, ptyc binary, mocktail. The
|
||||||
|
diff is gloriously red and the coverage denominator thanks you.
|
||||||
|
5. **Tag your releases.** Five releases of honest changelog work are currently
|
||||||
|
unaddressable commits.
|
||||||
|
6. Then go build the **Active Ticket chip** (S!) and the **Context X-ray**, and
|
||||||
|
let clide start showing people things no other IDE can.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Findings methodology: every medium/high claim above survived an independent
|
||||||
|
adversarial re-read of the cited lines (one claim did not — the proposed
|
||||||
|
"can't-disable core extensions" guard, which D-14 deliberately rejects, so it
|
||||||
|
stays out of this report). The full per-finding evidence, severities, and
|
||||||
|
suggested fixes live in the review transcripts; ~34 additional low-severity
|
||||||
|
findings were verified by citation only.*
|
||||||
|
|
||||||
|
*— Fable, 2026-06-11*
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user