This file provides guidance to AI coding agents when working with code in this repository.
Before making changes, read these repo docs:
docs/product.mdfor product principles and UX directiondocs/engineering.mdfor stack, file structure, and engineering boundaries
AGENTS.md is the source of truth for coding conventions and agent workflow rules.
Build the web app:
bun run build:webThis command:
- Removes the existing
_sitedirectory - Copies static files from
static/to_site/ - Runs the Rettangoli CLI to build the frontend bundle to
_site/public/main.js
Notes:
bun run buildmay not exist in this repo; usebuild:webfor validation.- Do not run
bun run build:webafter each change. The user is expected to be running a watch-mode session during active development. - Before pushing, run lint/format checks (the push hook also enforces this).
Run tests:
bun run test:smoke
bun run test:integration
bun run test:convergence
bun run test:collab-adapters
bun run test:putyNotes:
bun run test:putyruns the YAML-based Puty storage suite intests/puty/.- Run a single Puty scenario with
bunx vitest run tests/puty/<file>.spec.yaml. - The Puty suite is the preferred place for SQLite-backed storage assertions:
input commands live in YAML
in, expected committed rows live in YAMLout. - The shared Puty helper for storage scenarios is
tests/puty/insiemeStorageScenario.js. - The
scripts/test-*.jsfiles remain the home for non-Puty runtime, integration, convergence, and command-contract coverage.
This project uses a custom frontend framework based on 3 component files: view, store, handlers:
Read the links from the following files to familiarize with the code before starting to write any code.
If you need deeper or broader Rettangoli framework reference material, use
https://rettangoli.dev/llms.txt as the framework reference index.
- Prefer direct property access or nullish coalescing (
??) for defaults. - Use
??for default values instead of||when setting state/object fields. - Avoid defensive
typeof x === "string" ? x : ""patterns unless runtime type narrowing is truly required. - Prefer
undefinedovernull. - Avoid explicit
= nullinitialization; uselet value;when a later assignment is expected. - If state already guarantees a value, use it directly instead of re-normalizing with fallback checks.
- Do not build objects with conditional object-spread patterns such as
...(condition ? { field } : {})or nested spread-based payload builders. Build a local object and assign fields explicitly. - In Immer-backed store actions, prefer direct mutation of nested state fields over recreating nested objects with spread. Write
state.dialog.open = false, notstate.dialog = { ...state.dialog, open: false }.
- Keep page/component handlers simple and orchestration-focused.
- Keep page
*.store.jsand*.handlers.jsas the composition root for the page. They should stay easy to find and should wire components and shared helpers together explicitly. - Push domain logic, validation, and async complexity into services.
- Route-level async setup/loading should be handled in app-level orchestration, not repeated in page handlers.
- Prefer single-purpose store actions (
setCurrentProject, etc.) over multiple related setter calls. - Do not call browser globals like
documentorwindowdirectly from page handlers for cross-cutting side effects. - If a handler needs browser-side behavior such as blurring the active element, global focus changes, history changes, or global listeners, route that through
deps/servicesordeps/clients. - Keep low-level DOM-heavy behavior in
src/primitives/or in components that own that DOM surface directly. src/internal/ui/is the shared home for app-owned page/store/handler orchestration.src/internal/project/is reserved for pure project semantics only.- Small pure app-owned helpers that are neither project semantics nor UI orchestration belong in
src/internal/. src/deps/features/andsrc/deps/infra/are legacy folders. Do not add new code there.
- Use
rvn-detail-viewfor read-only right panels (instead of read-only forms). - Build read-only data in store as
detailFields(types:text,description,slot). - Prefer
textovertext-inlineunless explicitly required. - Keep custom interactive UI (preview button, lists, actions) as slots inside
rvn-detail-view. - Place panel title/header outside
rvn-detail-viewwhen needed. - For edit flows, use a dialog form opened from the detail panel (do not edit inline in read-only detail view).
- When opening edit dialogs, prefill explicitly after render:
editForm.reset()editForm.setValues({ values })
- At the top of handlers, destructure what you use from
deps(const { store, refs, appService, projectService } = deps) instead of repeatedly callingdeps.store,deps.refs, and similar dotted access throughout the function body. - Do not add dynamic form method wrappers (for example,
callFormMethod) or retry loops to wait for refs. - Call known methods directly (
formRef.reset(),formRef.setValues(...)). - Avoid defensive guard noise when data contract is already stable.
- Define/deconstruct refs at the top of handlers for clarity.
- Do not use module-scoped mutable runtime state in
*.handlers.js(let cleanupX, timers, mutable caches, drag state, etc). - Do not store handler runtime state on
refs.__...Runtime. - For async project or collab subscriptions, prefer RxJS streams/subscriptions mounted from
handleBeforeMount. - For plain local non-render values that must survive within one mounted instance, prefer top-level store fields with explicit actions/selectors over handler-owned runtime bags.
- Do not store cleanup functions, callbacks, or service instances in store.
- Use store only for plain local values in this case (for example timer ids, cache entries, pending ids).
- For project-backed page sync, prefer
createProjectStateStream(...)overhandleAfterMount+ stored unsubscribe handles. projectService.subscribeProjectState(...)is synchronous and assumes app/route orchestration has already ensured the repository.- For short-lived app-level event windows such as key chords, prefer RxJS stream composition over handler-owned timer/runtime bags.
- Use one canonical event payload shape per handler; remove multi-fallback id extraction once event contract is known.
- If two UIs emit similar events with different responsibilities (for example left explorer vs right list), use separate handlers to avoid recursion and side effects.
- Do not silently swallow errors with
console.erroronly for user actions; show user-facing feedback viaappService.showToast(...). - Prefer stable, explicit toast messages over raw
error?.messagetext. - If async picker/upload fails, toast and return early. Do not continue with partial state.
- For project click handlers, read project ids from
event.currentTarget.dataset.projectIdonly. - Do not parse ids from element
idas a fallback. - In stable handlers, prefer direct destructuring from event detail (for example
const { itemId } = payload._event.detail).
- Use
appService.pickFiles(...)as the single entry point for selecting files in handlers. multiple: falsereturns a single file object or no value (cancelled), not an array.multiple: truereturns an array.- Use picker-level validations:
validations: [{ type: "square" }]instead of duplicating inline handler validation. - Use picker-level upload when needed:
upload: true. - When
upload: true, read upload metadata from the returned file object (uploadSucessfulanduploadResult) instead of calling upload service directly in handlers.
- When selecting an item in custom center/right views, sync left explorer selection with
fileExplorer.selectItem({ itemId }). - Folder selections should clear item selection with
undefined, notnull. - Hover styling must not override selected styling.
- When enabling a
repositoryTargetin file explorer, ensure all relevant actions support it:- create folder
- rename
- delete
- duplicate
- move/reorder (
handleTargetChanged)
variablessupports folder tree operations, including drag/drop move.
- Project
name,description, andiconFileIdare owned by the project-specific DBappstore asprojectInfo, not repository/insieme state. - Global app-level
projectEntrieskeep duplicated cached values for project listing and discovery, not source-of-truth ownership. - For an opened project, read and write those fields through
projectService(getCurrentProjectInfo,updateCurrentProjectInfo), notappService.getCurrentProjectEntry().
Put it in src/pages/<page-name>/ with:
<page>.view.yaml<page>.store.js<page>.handlers.js
Page-private supporting code that is not a visual component and not shared across pages should live in:
src/pages/<page-name>/support/
Keep support/ flat. Do not add extra nested folders under support/. If a
piece of code grows into a real visual workflow or UI surface, promote it into
its own component instead of adding another level under support/.
If it is a route-level page, also wire it into:
src/pages/app/app.view.yamlsrc/pages/app/app.store.js
Put it in src/components/<component-name>/.
If it is not truly reusable, keep it close to the page instead of forcing an abstraction.
For page-local visual workflows that own a real UI surface and local state, prefer a dedicated component over a non-visual slice module.
Components must not import from other component folders. If code is shared
between multiple components in one page, keep it in that page's support/
folder or another non-component shared location.
Put it in src/primitives/ when the code owns low-level DOM behavior or a browser-native custom element.
Put it in src/internal/ui/* when the code is shared across pages, may touch stores/refs/render/RxJS/services, and is not a visual component or primitive.
Do not put page-private support code in src/internal/ui/*. Keep that code in
the owning page folder under support/.
Put it in src/internal/* when the code is pure, shared, app-owned, and is neither project semantics nor UI orchestration.
Put it behind:
appServiceprojectService
Prefer extending an existing coarse facade over adding a random helper file.
If code needs store selectors/setters, refs, render(), or page event payloads, it is not service code and belongs in src/internal/ui/* or a page instead.
Put it in src/internal/project/.
src/internal/project/ is intentionally kept merged into these canonical files:
commands.jsstate.jsprojection.jstree.jslayout.js
Do not create sparse new src/internal/project/* files unless explicitly approved.
Put it in:
src/deps/clients/*for low-level platform and external adapterssrc/deps/services/web/*orsrc/deps/services/tauri/*for service adapterssrc-tauri/for native desktop-shell behavior such as Tauri commands, plugin setup, packaging, updater wiring, and Rust-side integration
If the code wraps router, DB, file picker, updater, browser storage, or similar external/platform APIs directly, it belongs in src/deps/clients/*.
Put shared sync/session behavior in src/deps/services/shared/collab/.
Put web transport/runtime wiring in src/deps/services/web/collab/.