From dd27a508e56f73aac1fc84bf85ed29448e7dacf2 Mon Sep 17 00:00:00 2001 From: Ammar Abou Zor Date: Fri, 25 Sep 2026 12:36:23 +0200 Subject: [PATCH 1/6] Add release workflow for Developers and Agents * Add release workflows to .ai directory and wire. * Release workflow is maid for both developers and agents, and it covers each step of the current release workflow --- .ai/INDEX.md | 5 +- .ai/workflows/INDEX.md | 11 ++++ .ai/workflows/release.md | 108 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 .ai/workflows/INDEX.md create mode 100644 .ai/workflows/release.md diff --git a/.ai/INDEX.md b/.ai/INDEX.md index 1e4f4a191a..69327188ec 100644 --- a/.ai/INDEX.md +++ b/.ai/INDEX.md @@ -3,8 +3,8 @@ ## Overview This is the canonical entrypoint for repository AI context. -Read this file first at the start of each session, then use the knowledge index to choose the relevant context files. -Repository AI context lives under `.ai/knowledge/`. +Read this file first at the start of each session, then use the indexes below to choose the relevant files. +Repository AI context lives under `.ai/knowledge/`, and recurring maintenance processes under `.ai/workflows/`. ## Start Here @@ -14,3 +14,4 @@ Repository AI context lives under `.ai/knowledge/`. ## Navigation - Module and target context: `.ai/knowledge/INDEX.md` +- Recurring maintenance processes: `.ai/workflows/INDEX.md` diff --git a/.ai/workflows/INDEX.md b/.ai/workflows/INDEX.md new file mode 100644 index 0000000000..63354862b7 --- /dev/null +++ b/.ai/workflows/INDEX.md @@ -0,0 +1,11 @@ +# Chipmunk Workflows Index + +## Overview + +Repository workflows live under `.ai/workflows/`. +Each file describes one recurring maintenance process end to end, for developers and agents alike. +Use this file to choose the workflow for the current task. Load a workflow only when the task is that process. + +## Workflows + +- Publishing a new version of the application: `.ai/workflows/release.md` diff --git a/.ai/workflows/release.md b/.ai/workflows/release.md new file mode 100644 index 0000000000..6b9c281ffd --- /dev/null +++ b/.ai/workflows/release.md @@ -0,0 +1,108 @@ +# Release Workflow + +## Overview + +A release ships the GUI application and the CLI tool together from one version tag. +The tag triggers CI, which publishes a GitHub pre-release with the build artifacts. +The pre-release is promoted to the latest release only after it has been tested, because promotion is what notifies users through the in-app updater. + +## Remotes and Roles + +The steps below assume the following setup. Adapt the remote names if the local setup differs. + +- `origin`: maintainer fork. Release branches and pull requests start here. +- `upstream` (`esrlabs/chipmunk`): main repository. Tags, releases, and release notes live here. + +## Agent Boundaries + +An agent prepares content only: version bumps, `Cargo.lock` refresh, changelog entries, and the release notes text. + +Agents do not commit, push, tag, or touch GitHub releases. Every git and GitHub step in this workflow belongs to the maintainer. +For those steps, propose the exact commands to run instead of running them. This applies to tagging in particular, where a wrong or premature tag immediately starts the release CI. + +## 1. Release Branch + +Create a branch on the fork named after the version, for example `release-4-4-0`. +Commit and pull request title follow the existing convention: `Up to Version `. No description is needed. + +## 2. Version Bumps + +- GUI and workspace crates: `version` under `[workspace.package]` in the root `Cargo.toml`. +- CLI: `version` in `crates/cli/Cargo.toml`. The CLI is versioned independently and is bumped only when it changed. +- Refresh `Cargo.lock` by running any cargo command that resolves the workspace, for example `cargo check`. + +## 3. Changelogs + +Two changelogs are maintained: + +- `changelog.md` (repository root): the user-facing GUI changelog. It is also the source for release notes and is presented to users inside the application. +- `crates/cli/CHANGELOG.md`: the CLI changelog. + +Format used by both: a version heading `# (DD.MM.YYYY)`, followed by `## Features`, `## Changes`, and `## Fixes` sections, including only the sections that apply. + +Writing rules: + +- Write for users, not for developers. Describe observable behavior, not implementation. +- Read the previous entries before writing. They define the expected tone, level of detail, and phrasing. +- Group several small related changes into one entry instead of listing every commit. +- When the CLI version changed, mention it in the main changelog as part of the relevant entry, including the new CLI version number. + +## 4. Pull Request + +Open the pull request against `upstream/master` and merge it once the checks pass. + +## 5. Tag and Release CI + +Create the tag on `master` in the upstream repository. Tag names are the bare version, for example `4.4.0`, with no prefix. + +`.github/workflows/release.yml` runs on any pushed tag and: + +- creates a GitHub release named after the tag, marked as a pre-release, +- builds on Linux, Windows, and both macOS architectures, with signing and notarization on macOS, +- uploads the portable archives and the installers (`.tgz`, `.deb`, `.rpm`, `.msi`, `.pkg`) to the release. + +## 6. Pre-Release Testing + +Every tag produces a pre-release, and it stays one until it has been tested. In that window the build is published with all its artifacts, but the updater does not offer it to users. + +How the updater picks a release, which is what makes this window possible: + +- On startup the application fetches the most recent releases of `esrlabs/chipmunk` from the GitHub API. +- Draft releases are always ignored. Pre-releases are ignored as well, unless the user enabled the `Check pre-releases` option in the application update settings, which is off by default and only available while update checks are enabled. +- Only releases with the same major version as the running application are offered as updates. +- Implementation: `crates/app/src/host/service/update/`. + +Anyone who enables that option updates to the pre-release like a normal user would. This is what the window is for: the new build, its artifacts, and the update path itself are exercised in their real form while users are still on the previous version. + +Testing therefore belongs after the workflow finished and its artifacts are attached, and before promotion. Writing the release notes first (step 7) puts them into the same test, since they are shown by the update that is being tested. + +## 7. Release Notes + +The release notes are the user-facing text of the release. The application fetches the notes of the release whose tag matches the running version and shows them, rendered as markdown, on the first start after an update. + +They are shown once. A user who has already started the new version will not see them again, so editing the notes after promotion does not reach them. The notes must be correct before promotion. + +Compose them from `changelog.md`: + +1. Copy the section of the version being released as it is, including its heading with the version and date. +2. Append the sections of a few previous releases below it, each with its headings demoted by one level (`#` becomes `##`, `##` becomes `###`), so the released version remains the only top-level heading. This gives users who skipped versions the changes they missed. +3. Keep to plain markdown: headings, bullet lists, inline code, and bold. The in-app viewer is not GitHub, so avoid raw HTML and GitHub-specific syntax. + +When in doubt, open the previous releases on GitHub next to `changelog.md` and compare them. The difference between the two shows exactly how much history is usually included and how the headings are shifted. + +## 8. Promotion + +Promotion is the step that makes the release public to the updater. Before doing it, confirm the release notes are final and the build passed the tests from step 6. + +1. On the GitHub release, remove the pre-release mark and set it as the latest release. +2. From this moment the updater offers the version to every user who has update checks enabled, without the pre-release option. + +## Recovering From a Failed Release + +If the release workflow fails or a problem is found while the release is still a pre-release: + +1. Delete the release on GitHub. +2. Delete the tag on GitHub. +3. Land the fixes and push a new tag. + +Fixes are usually committed directly, without a dedicated pull request, unless they are large enough to warrant a review. From e84296e0aeb94723759e9f69ff136e0efbd7e38e Mon Sep 17 00:00:00 2001 From: Ammar Abou Zor Date: Fri, 25 Sep 2026 15:33:09 +0200 Subject: [PATCH 2/6] Add development rules for agents. Add general rust development and specific UI rules for agents to be considered in development and review processes. --- .ai/INDEX.md | 5 ++++- .ai/rules/INDEX.md | 13 +++++++++++ .ai/rules/app-ui.md | 53 +++++++++++++++++++++++++++++++++++++++++++++ .ai/rules/rust.md | 49 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 .ai/rules/INDEX.md create mode 100644 .ai/rules/app-ui.md create mode 100644 .ai/rules/rust.md diff --git a/.ai/INDEX.md b/.ai/INDEX.md index 69327188ec..b62d353871 100644 --- a/.ai/INDEX.md +++ b/.ai/INDEX.md @@ -4,14 +4,17 @@ This is the canonical entrypoint for repository AI context. Read this file first at the start of each session, then use the indexes below to choose the relevant files. -Repository AI context lives under `.ai/knowledge/`, and recurring maintenance processes under `.ai/workflows/`. +Repository AI context lives under `.ai/knowledge/`, binding code rules under `.ai/rules/`, and recurring maintenance processes under `.ai/workflows/`. ## Start Here 1. `.ai/INDEX.md` (this file) 2. `.ai/knowledge/INDEX.md` (context map and repository guardrails) +Before writing or reviewing code, read `.ai/rules/INDEX.md`. + ## Navigation - Module and target context: `.ai/knowledge/INDEX.md` +- Code rules for implementation and review: `.ai/rules/INDEX.md` - Recurring maintenance processes: `.ai/workflows/INDEX.md` diff --git a/.ai/rules/INDEX.md b/.ai/rules/INDEX.md new file mode 100644 index 0000000000..24042c01a4 --- /dev/null +++ b/.ai/rules/INDEX.md @@ -0,0 +1,13 @@ +# Chipmunk Rules Index + +## Overview + +Repository code rules live under `.ai/rules/`. +They are binding for every code change and every review in this repository, and they apply on top of the context in `.ai/knowledge/`. +Use this file to choose the rule files that cover the areas being touched. +A section marked `Red Flag` is a violation a review must report, even when the rest of the change is sound. + +## Rules + +- All Rust code: `.ai/rules/rust.md` +- GUI application UI code: `.ai/rules/app-ui.md` diff --git a/.ai/rules/app-ui.md b/.ai/rules/app-ui.md new file mode 100644 index 0000000000..446db6e5c7 --- /dev/null +++ b/.ai/rules/app-ui.md @@ -0,0 +1,53 @@ +# GUI Application UI Rules + +## Scope + +Applies to `egui` and `eframe` code, which lives in the GUI application crate `crates/app`. +It applies on top of `.ai/rules/rust.md`, which holds for this code as well. + +## The Frame Loop Is Hot + +`update` and every `ui` path run on each frame. Keep out of them: + +- compute-heavy work, +- per-frame allocation, +- eager formatting of text that may never be shown, +- derived values recomputed from scratch on every frame. + +Instead: + +- Move work off the UI thread when its result does not have to be recomputed every frame. +- Cache reusable derived values such as validation results, filtered views, and formatted summaries, and invalidate them when their inputs change. + +## Red Flag: IO and Blocking Calls in the Render Loop + +Never perform IO in `update` or in any `ui` path: file reads and writes, directory scans, file metadata queries, network requests, or spawning processes. The same holds for every other blocking call, such as waiting on a channel or a lock, or driving a future with `block_on`. + +Each of these stalls the frame and freezes the window for the duration of the call. The cost stays invisible on a fast local machine with a warm cache, and appears on large files, slow or remote storage, and unreachable endpoints. + +Run the work in a host or session service instead, wrapping synchronous IO in `spawn_blocking` so the async runtime is not blocked either, and deliver the result to the UI as a `HostMessage` or `SessionMessage`. The UI renders from the state that message updated. + +Sending a message must also wake the UI, otherwise the result stays invisible until an unrelated frame happens to be drawn. `evaluate_send_res` in `crates/app/src/common/comm_utls.rs` requests the repaint on a successful send. Use it rather than a hand-rolled channel send, on the service side as well. + +The one sanctioned exception is grabbing log lines for the table being drawn, in `TableDelegate::prepare` of the logs and search tables. Those rows have to reach the screen in the frame that asks for them, so the grab blocks, with a retry and a timeout. Nothing else in the render loop may block. + +## Commands and Failures + +- Send commands from the UI through `UiActions::try_send_command` or `send_command_with_retry`, not through a channel sender directly. The helper logs the failure and raises a notification, while a raw send drops the user action silently. +- Surface failures as an `AppNotification` together with a log entry. Widgets do not render error text inline. `Session::ok_or_notify` is the standard log-then-notify adapter. + +## Shared UI Code + +Before writing a UI building block, look for an existing one. Shared UI code lives in `crates/app/src/common/ui/`, `crates/app/src/host/common/`, and the `shared/` modules of each area. Reusing it keeps styling and behavior consistent across the app, while a local reimplementation drifts from the rest of it. + +Native file dialogs go through `actions.file_dialog` in `crates/app/src/host/ui/actions/file_dialog.rs`. + +## State Placement + +- Keep state placement explicit. In immediate mode nothing survives a frame unless it is stored deliberately. +- Persist state that must outlive the frame instead of rebuilding it inside transient UI branches, unless resetting it is the intent. + +## Tooltips + +- Use `Response::on_hover_ui(...)` instead of `on_hover_text(...)` when the content is computed or formatted, so it is built only on hover. +- Inside `on_hover_ui(...)`, call `ui.set_max_width(ui.spacing().tooltip_width)` before adding content to preserve egui's default tooltip wrapping. diff --git a/.ai/rules/rust.md b/.ai/rules/rust.md new file mode 100644 index 0000000000..d6a5fabe94 --- /dev/null +++ b/.ai/rules/rust.md @@ -0,0 +1,49 @@ +# Rust Rules + +## Scope + +Applies to all Rust code in this repository. +GUI code follows `.ai/rules/app-ui.md` in addition to this file. + +## Design and Scope + +- Prefer the smallest correct change that fully solves the problem. Between valid approaches, take the one with less incidental complexity. +- Keep behavior in the module or type that already owns the relevant state and responsibility. +- Fix, replace, or remove a problematic design instead of wrapping it in compatibility layers, adapters, flags, or one-off branches. + +## Naming + +- Name every item for its domain role: what a function does, what a type represents, what a value means — not for the call site that uses it, its storage type, or the implementation phase it belongs to. + +## Correctness and Performance + +- Keep async code non-blocking. Isolate blocking calls explicitly, for example with `spawn_blocking`. +- In compare, validate, reset, and sync logic, destructure structs and match enums exhaustively, without `..` rest patterns or catch-all arms, so that a new field or variant fails to compile and forces the code to be revisited. +- Do not derive `Copy` by default. Reserve it for small value types where implicit duplication is obviously cheap, and use `Clone` otherwise. + +## Red Flag: User-Facing Text in Logic + +Never implement or preserve application logic that matches, parses, or compares human-readable text: error messages, notification and toast copy, dialog copy, labels, or any other user-facing string. Carry that information in types instead. + +## Modules and Visibility + +- Split a file at a clear submodule boundary as it grows. Files beyond roughly 550 lines usually have one. +- Treat `pub(crate)`, `pub(super)`, and `pub(in ...)` as exceptions with a concrete reason, not as safer defaults. Prefer plain `pub`. +- Do not repeat scoped visibility on the inherent methods of a type that already defines the visibility boundary. +- Prefer `use` statements over fully qualified paths, and keep full paths only for disambiguation. Order imports: standard library, external crates, workspace crates, then `crate`, `self`, and `super`. + +## Documentation and Comments + +- Write `//!` module docs, and `///` docs for public types, public fields, and public functions. +- Keep `///` docs on the external contract and observable behavior. Explain implementation, control flow, and borrow reasoning in `//` comments inside the body. +- Comment non-obvious intent, invariants, edge cases, unsafe blocks, concurrency assumptions, and code shaped to avoid a specific bug or regression. Do not comment what the code already says. +- Document functions returning `bool` unless the name makes `true` and `false` unambiguous. +- No emojis in code or comments. + +## Tests + +- A test must protect a real behavior regression. If it does not, drop it and pick a higher-signal scenario. +- Do not add or keep low-signal tests: constructor storage checks, passthrough wrappers, shape-only assertions, or getter and setter coverage. +- Delete or update tests that survive only to preserve obsolete implementation details. +- Keep tests deterministic. Control clocks, randomness, and external effects. +- Do not change production code only for testability without asking first. From b57a0eb6cb4421f757e30065fb0dcd29c35c2927 Mon Sep 17 00:00:00 2001 From: Ammar Abou Zor Date: Mon, 28 Sep 2026 15:05:37 +0200 Subject: [PATCH 3/6] Add application architecture documentation + Wire it up with the existing documentation files. --- .ai/knowledge/crates/app/AGENTS.md | 24 +++--- .ai/knowledge/crates/app/architecture.md | 101 +++++++++++++++++++++++ .ai/rules/app-ui.md | 1 + 3 files changed, 114 insertions(+), 12 deletions(-) create mode 100644 .ai/knowledge/crates/app/architecture.md diff --git a/.ai/knowledge/crates/app/AGENTS.md b/.ai/knowledge/crates/app/AGENTS.md index 7eb3f796dc..1084e1f6a4 100644 --- a/.ai/knowledge/crates/app/AGENTS.md +++ b/.ai/knowledge/crates/app/AGENTS.md @@ -7,13 +7,14 @@ It owns the desktop application shell and integrates directly with the core runt ## Start Here -1. `crates/app/src/host/ui/` -2. `crates/app/src/session/ui/` -3. `crates/app/src/host/service/` -4. `crates/app/src/session/service/` -5. `crates/app/src/host/ui/state/` -6. `.ai/knowledge/crates/core/AGENTS.md` -7. `.ai/knowledge/crates/stypes/AGENTS.md` +1. `.ai/knowledge/crates/app/architecture.md` — read before any change that crosses the UI/service boundary, adds an extension point, or is a design review. +2. `crates/app/src/host/ui/` +3. `crates/app/src/session/ui/` +4. `crates/app/src/host/service/` +5. `crates/app/src/session/service/` +6. `crates/app/src/host/ui/state/` +7. `.ai/knowledge/crates/core/AGENTS.md` +8. `.ai/knowledge/crates/stypes/AGENTS.md` ## If You Need X, Start Here @@ -36,20 +37,19 @@ It owns the desktop application shell and integrates directly with the core runt - Command palette and shortcuts: `crates/app/src/host/ui/command_palette/`, `crates/app/src/host/ui/shortcuts/` - Built-in app updater/release checks: `crates/app/src/host/service/update/`, `crates/app/src/host/ui/update.rs`, `crates/app/src/host/ui/state/info.rs`, `crates/app/src/host/ui/banners/update.rs`, `crates/app/src/host/ui/menu.rs`, `crates/app/src/host/service/mod.rs`, `crates/app/src/host/command.rs`, and `crates/app/src/host/message.rs` -## Ownership Model +## State Owners + +For the ownership model and the channels between UI and services, see `.ai/knowledge/crates/app/architecture.md`. -- UI state structs are the source of truth for native application/UI state. -- Services do not own canonical app/UI state; they only run backend work and copied request data. - `Host` is the `eframe::App` and owns top-level rendering, message polling, tabs, storage, notifications, and global UI actions. - `HostState` owns active tabs, open sessions, setup tabs, preferences, shortcuts, app info, top-level modals, plugin UI state, and `HostRegistry`. - `HostRegistry` owns global filter/search-value definitions and presets. - `HostStorage` owns UI-facing service-backed storage domains: recent sessions, file explorer/favorite folders, and app settings. Presets stay in `HostRegistry`, which hands out their save snapshot while `HostStorage` collects one. - `persist` owns only lightweight host UI preferences saved through `eframe` storage. -- `PluginService` owns `PluginsManager`; UI code consumes published `PluginsState` and must not load or mutate plugin runtime state directly. +- `PluginService` owns `PluginsManager`; the UI consumes published `PluginsState`. - `Session` owns one live session UI shell and its component UIs. - `SessionShared` owns canonical per-session state: applied filters, temp search, search/chart operation state, log selection, bookmarks, observe state, attachments, exports, signals, and view state. - Component UIs should keep only local render/edit/cache state unless they already own a specific domain. -- Backend/UI integration crosses `HostCommand`/`HostMessage` and `SessionCommand`/`SessionMessage`. ## Coding Guidelines diff --git a/.ai/knowledge/crates/app/architecture.md b/.ai/knowledge/crates/app/architecture.md new file mode 100644 index 0000000000..098c73c31d --- /dev/null +++ b/.ai/knowledge/crates/app/architecture.md @@ -0,0 +1,101 @@ +# Native GUI Architecture + +## Overview + +`crates/app` runs as a single process: the UI thread runs one `eframe::App`, and all async work runs on one tokio runtime on one spawned thread. There is no separate backend process; the core crates are linked directly. + +Read this file before adding a feature, crossing the UI/service boundary, or reviewing a design. +Binding code rules live in `.ai/rules/app-ui.md`. + +## Frame Loop + +The workspace patches `eframe`/`egui` to a fork that splits `App::logic` from `App::ui`: + +- `logic` drains the host message and notification channels, polls the pending storage save, then pumps the messages of **every** session tab. +- `ui` renders **only** the active tab. + +Background sessions stay alive in state but are never drawn. Message handling belongs in `logic` or `Session::handle_messages`; putting it in render code starves every inactive session. + +## Host and Sessions + +`Host` is the `eframe::App` and owns tabs, top-level state, storage, notifications, and global actions. `HostTabs` owns the tab vector: Home is index 0 and cannot be closed, and the session count is unbounded. + +A session is a pair joined by its own channel pair: `Session` (UI) and `SessionService`, which wraps one `session_core::Session`. + +- **Create:** `HostCommand::StartSession` → `HostService::start_session` → `SessionService::spawn` → `HostMessage::SessionCreated` → `Host` builds the `Session` and `HostTabs` adds it, replacing the originating setup tab in place when there is one. +- **Tab close:** registry cleanup, remove the tab, fire-and-forget `CloseSession`. +- **App exit:** close every receiver, send `CloseSession` with an ack channel, wait on all acks under one shared deadline, flush storage, then `HostCommand::OnShutdown`. + +## Communication + +Four async channels: + +- `HostCommand` — UI to host service. +- `HostMessage` — host service to UI. +- `AppNotification` — service to UI, shown as banners and kept in notification history. +- `HostAsyncEvent` — internal to the service layer: results of background work the host command loop itself must act on. Never reaches the UI. + +Per session, `SessionCommand` and `SessionMessage` mirror the first two. + +Services never send on a raw sender. Every UI-bound send goes through `ServiceSenders`, which requests a repaint on success; a raw send arrives but may not be drawn until an unrelated event wakes the UI. + +The one exception to async messaging is a `std::sync::mpsc::Sender` embedded in a command, used where the caller must block for the answer: the blocking line grabs and the shutdown acknowledgement. + +## Service Layer + +`HostService::run` is the only host command loop, selecting over incoming commands and `HostAsyncEvent`. Startup is a blocking handshake: `HostService::spawn` returns `HostServiceInit` over a sync channel, so the first frame already has recent sessions, app settings, and presets. Heavier domains load lazily afterwards. + +Sub-services come in two shapes. Work whose result the host loop must act on is a struct initialized with the `HostAsyncEvent` sender (`StorageService`, `PluginService`). Work that only updates the UI is free functions taking `ServiceSenders` by value (`update`). `session/service/export.rs` and `tracker.rs` are not services; they are `impl SessionService` files. + +A service may own backend runtime state — `PluginsManager`, `session_core::Session`, the instance temp directory, the operation tracker — but never state the UI renders from. It publishes snapshots instead. + +## One Round Trip + +Applying a search filter, end to end: + +1. The search bar calls `SessionShared::sync_search`. +2. Shared state resolves the session's filter ids against the host `FilterRegistry`, drops the in-flight operation, recompiles the filters, mints a new operation id, and returns `DropSearch` + `ApplySearchFilter`. +3. The widget dispatches each through `UiActions`. +4. `SessionService::handle_command` calls into `session_core`. +5. Core callbacks are translated into `SessionMessage`s. +6. The next `logic` applies them to `shared.search`; the next `ui` renders counts and pulls row text. + +## State Ownership + +- **UI-canonical:** `HostState`, `HostTabs`, `HostStorage`, and one `SessionShared` per session. These are the source of truth. +- **Backend-canonical, mirrored in the UI:** log content, search results, bookmarks, attachments. Bookmarks are the clearest case: a toggle sends a command and only the resulting message mutates state. Never write these optimistically. This category exists because the current core owns that state, not by design; the intended direction is UI-canonical, so do not grow it. +- **Derived caches:** compiled filters, table row caches, the published plugin snapshot. Invalidate on explicit state changes, never by recomputing in render. + +Persistence uses two separate stores: `host/ui/persist.rs` writes lightweight host preferences through eframe storage; everything heavier goes `HostStorage` → `HostCommand::SaveStorage` → the storage worker, driven by per-domain dirty flags. + +## Boundary to the Workspace + +`crates/stypes` holds the cross-crate contracts, `crates/core` is the engine, and `crates/app` owns presentation plus the *editable* config model (`ParserConfig`, `ByteSourceConfig`, `StreamConfig`). Those configs convert to `stypes` at a single point, `HostService::start_session`. + +Worth knowing: the config and storage types live under `host/ui/`, and the service layer imports them. `ui` is not a leaf layer — it is also the shared data-model module. + +Plugins follow the ownership rule strictly. `PluginsManager` exists only inside `PluginService`; the UI consumes cloned `PluginsState` snapshots and mutates through commands only. Plugin render metadata crosses as a `LogSchemaSpec`, resolved once by the host service and frozen into the session's `LogSchema` at creation, so an open session's columns cannot change when plugins reload. + +## Extension Points + +Ordered touch sequences: + +- **Parser:** `host/common/parsers.rs` (names and compatibility) → `host/ui/session_setup/state/parsers/` (`ParserConfig`) → side and main config forms → `HostService` conversion to `ParserType` and `LogSchemaSpec` → a new `session/ui/definitions/schema/` implementation. +- **Source/transport:** `host/common/sources.rs` → parser compatibility → `session_setup/state/sources/` (`StreamConfig`) → main config form → `HostService` conversion to `ObserveOrigin`. +- **Session operation:** `session/command.rs` → `session/message.rs` → `SessionService::handle_command` → `Session::handle_messages` → the sending widget. +- **Host command:** `host/command.rs` → `HostService::handle_command` → optional `host/message.rs` → `Host::handle_message` → the sender. +- **Shortcut:** no central enum; add a named field to `AppShortcuts` or `SessionShortcuts` and handle it. Deferred work needs a `ShortcutAction` variant. +- **Palette command:** `CommandAction` variant → `scope()` → `execute_action` → the `COMMANDS` slice. +- **Bottom tab:** `bottom_panel/tab_types.rs` → `render_content`, the tabs array, `bottom_tab_from_index`, and a field on `BottomPanelUI`. +- **Storage domain:** `host/ui/storage/types.rs` → `HostStorage` collection and dirty tracking → `host/service/storage/` with a disk-IO submodule. + +## Traps + +These compile and fail at runtime or silently: + +- Mutating `SessionShared` public fields directly skips `bump_recent_revision`: the change renders but never persists into the recent session. Use the wrapper methods. +- `shared.signals` is a render-frame queue. Emitting a signal from message handling or restore leaks it into the next frame; `debug_assert!`s guard this. Outside render use `sync_search_outcome`, not `sync_search`. +- Compiled filter order must match the order sent in `ApplySearchFilter`, or match highlight colors land on the wrong filter. +- A bottom tab added without extending `bottom_tab_from_index` panics at runtime instead of failing to compile. +- A storage domain missing from dirty collection never persists; one missing from the failure re-mark never retries. +- A palette command left out of the `COMMANDS` slice, or a shortcut left out of the definitions, is unreachable. diff --git a/.ai/rules/app-ui.md b/.ai/rules/app-ui.md index 446db6e5c7..6623e53e46 100644 --- a/.ai/rules/app-ui.md +++ b/.ai/rules/app-ui.md @@ -46,6 +46,7 @@ Native file dialogs go through `actions.file_dialog` in `crates/app/src/host/ui/ - Keep state placement explicit. In immediate mode nothing survives a frame unless it is stored deliberately. - Persist state that must outlive the frame instead of rebuilding it inside transient UI branches, unless resetting it is the intent. +- UI state is the source of truth. A service owns backend runtime resources and publishes snapshots; it does not own state the UI renders from. Where the core keeps that state instead, mirror it read-only and let the resulting message be the only writer. ## Tooltips From c1272dd9de512af35ee6121e01ea94a78e0e735d Mon Sep 17 00:00:00 2001 From: Ammar Abou Zor Date: Tue, 29 Sep 2026 08:21:40 +0200 Subject: [PATCH 4/6] Remove `just` commands restrictions Those restrictions are personal and shouldn't general rules for this repo. --- .ai/knowledge/INDEX.md | 4 ++-- .ai/knowledge/crates/app/AGENTS.md | 1 - .ai/knowledge/crates/cli/AGENTS.md | 1 - .ai/knowledge/crates/core/AGENTS.md | 1 - .ai/knowledge/crates/stypes/AGENTS.md | 1 - .ai/knowledge/plugins/AGENTS.md | 1 - 6 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.ai/knowledge/INDEX.md b/.ai/knowledge/INDEX.md index f5387aad61..834d8c3c81 100644 --- a/.ai/knowledge/INDEX.md +++ b/.ai/knowledge/INDEX.md @@ -9,8 +9,8 @@ Use this file to choose the next context file to read. Do not load every context - Main code lives in the root Rust workspace under `crates/`. - `plugins/` is intentionally outside the main workspace validation path. -- The root `justfile` is the developer entrypoint, but do not run `just` recipes unless the user explicitly asks for them. -- Run build, test, lint, or validation commands only when the user requests validation or when needed to verify a change. +- The root `justfile` is the developer entrypoint for build, validation, and run commands. +- `just validate`, aliased as `just pr`, runs formatting, check, clippy, and tests. It is the full gate CI runs on every pull request, so run it before proposing one. Use targeted `cargo -p ` commands while iterating. ## Primary Context diff --git a/.ai/knowledge/crates/app/AGENTS.md b/.ai/knowledge/crates/app/AGENTS.md index 1084e1f6a4..303895ac9b 100644 --- a/.ai/knowledge/crates/app/AGENTS.md +++ b/.ai/knowledge/crates/app/AGENTS.md @@ -73,7 +73,6 @@ For the ownership model and the channels between UI and services, see `.ai/knowl ## Validation -Run validation only when requested or needed to verify a change. Use targeted Cargo commands from the repository root, for example: - Check: `cargo check -p app` diff --git a/.ai/knowledge/crates/cli/AGENTS.md b/.ai/knowledge/crates/cli/AGENTS.md index 6a90436f43..04078a934b 100644 --- a/.ai/knowledge/crates/cli/AGENTS.md +++ b/.ai/knowledge/crates/cli/AGENTS.md @@ -41,7 +41,6 @@ It orchestrates core runtime crates and owns CLI argument parsing and output for ## Validation -Run validation only when requested or needed to verify a change. Use targeted Cargo commands from the repository root, for example: - Check: `cargo check -p cli` diff --git a/.ai/knowledge/crates/core/AGENTS.md b/.ai/knowledge/crates/core/AGENTS.md index 3c58be14f9..7abaf30cb9 100644 --- a/.ai/knowledge/crates/core/AGENTS.md +++ b/.ai/knowledge/crates/core/AGENTS.md @@ -67,7 +67,6 @@ The native app and CLI depend on these crates directly through Rust APIs. ## Validation -Run validation only when requested or needed to verify a change. Use targeted Cargo commands from the repository root, for example: - Check one crate: `cargo check -p ` diff --git a/.ai/knowledge/crates/stypes/AGENTS.md b/.ai/knowledge/crates/stypes/AGENTS.md index 1d834f538f..79803fe736 100644 --- a/.ai/knowledge/crates/stypes/AGENTS.md +++ b/.ai/knowledge/crates/stypes/AGENTS.md @@ -39,7 +39,6 @@ Treat changes here as cross-crate API changes. ## Validation -Run validation only when requested or needed to verify a change. Use targeted Cargo commands from the repository root, for example: - Check: `cargo check -p stypes` diff --git a/.ai/knowledge/plugins/AGENTS.md b/.ai/knowledge/plugins/AGENTS.md index ef9b738cce..b8731139fb 100644 --- a/.ai/knowledge/plugins/AGENTS.md +++ b/.ai/knowledge/plugins/AGENTS.md @@ -57,7 +57,6 @@ The plugin API crate is part of the main Rust workspace, while plugin examples a ## Validation -Run validation only when requested or needed to verify a change. Use targeted commands for the area changed: - Main workspace plugin API: `cargo test -p plugins_api` From 420542825a1fde0b3a054c68d6e59f42edfd3539 Mon Sep 17 00:00:00 2001 From: Ammar Abou Zor Date: Tue, 29 Sep 2026 08:31:15 +0200 Subject: [PATCH 5/6] Docs: Point to `.ai` directory in contributing `.ai` dir is the main point for structure, rules and tips --- docs/contributing/development-guide.md | 15 +++------------ docs/contributing/getting-started.md | 6 ++++++ docs/contributing/repository-structure.md | 2 ++ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/contributing/development-guide.md b/docs/contributing/development-guide.md index 537e381203..6325cf900d 100644 --- a/docs/contributing/development-guide.md +++ b/docs/contributing/development-guide.md @@ -22,20 +22,11 @@ just run -r -- path/to/file ## Dependency Management -For crates in the main workspace under `crates/`, declare dependency versions and internal crate paths only in the root `Cargo.toml` under `[workspace.dependencies]`. +Dependency versions and workspace configuration follow the rules in [`.ai/knowledge/rust-workspace.md`](https://github.com/esrlabs/chipmunk/blob/master/.ai/knowledge/rust-workspace.md), which is the single source of truth for them. -In crate-local `Cargo.toml` files, reference dependencies with `workspace = true` and keep them in the matching group: +## Code Rules -```toml -[dependencies] -# Internal crates -stypes.workspace = true - -# External crates -serde.workspace = true -``` - -Keep the root workspace dependency list grouped by internal crates, external dependencies, and development dependencies. +Changes and reviews follow the rules in [`.ai/rules/INDEX.md`](https://github.com/esrlabs/chipmunk/blob/master/.ai/rules/INDEX.md): `rust.md` for all Rust code, plus `app-ui.md` for the GUI crate. ## Formatting and Checks diff --git a/docs/contributing/getting-started.md b/docs/contributing/getting-started.md index 80c3c6f9c4..031b172113 100644 --- a/docs/contributing/getting-started.md +++ b/docs/contributing/getting-started.md @@ -26,6 +26,12 @@ just --list If the command prints the available recipes, your basic setup is ready. +## Repository Context + +`.ai/` holds the shared context of this repository, written for developers and agents alike: orientation on the modules in `knowledge/`, the code rules every change and review follows in `rules/`, and recurring maintenance processes in `workflows/`. + +Start at [`.ai/INDEX.md`](https://github.com/esrlabs/chipmunk/blob/master/.ai/INDEX.md), which links to the rest. Agents reach the same content through `AGENTS.md` in the repository root. + ## Next Steps Continue with [Repository Structure](./repository-structure.md) to understand the main project areas, then read the [Development Guide](./development-guide.md) for the common workflow. diff --git a/docs/contributing/repository-structure.md b/docs/contributing/repository-structure.md index e38e7aec93..5a47d66a22 100644 --- a/docs/contributing/repository-structure.md +++ b/docs/contributing/repository-structure.md @@ -10,3 +10,5 @@ The main Rust workspace lives under `crates/`. These are the most important area * `crates/bufread`, `crates/dir_checksum`, `crates/file_tools`, `crates/shell_tools`: utility crates used by the workspace. Plugin templates and examples live under `plugins/`. They are useful when creating or testing plugin integrations, but they are separate from the normal application development flow. + +Repository context for developers and agents lives in `.ai/`, introduced in [Getting Started](./getting-started.md#repository-context). From 51659bc64c42fcac32f58884a15038b59197e42b Mon Sep 17 00:00:00 2001 From: Ammar Abou Zor Date: Tue, 29 Sep 2026 08:34:23 +0200 Subject: [PATCH 6/6] Mention utility crates in workspace docs --- .ai/knowledge/rust-workspace.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.ai/knowledge/rust-workspace.md b/.ai/knowledge/rust-workspace.md index 7f6845426a..6f896afb14 100644 --- a/.ai/knowledge/rust-workspace.md +++ b/.ai/knowledge/rust-workspace.md @@ -16,3 +16,12 @@ serde.workspace = true ``` Keep the root workspace dependency list grouped by internal crates, external dependencies, and development dependencies. + +## Utility Crates + +Small, self-contained crates under `crates/`. Read the crate source directly; they need no separate context file. + +- `bufread`: buffered reader that refills while unread data is still buffered. +- `dir_checksum`: recursive directory checksums honoring `gitignore` rules. +- `file_tools`: file content classification, such as the UTF-8 text check. +- `shell_tools`: discovery of installed shell profiles on the current platform.