Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions docs/notes/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -3448,3 +3448,204 @@ rail without a restart — a write that stayed local would leave the rail showin
the project unpinned until reload.

Fonte: `src/chrome/useProjectMenu.tsx:useProjectMenu`

<a id="reorder-external-drop-contract"></a>

### The external drop target answers, the hook does not decide

`onMove` reports whether the pointer is over an external drop target and
`onDrop` whether that target consumed the drop. The boolean is the whole
contract: the hook previews and commits from it and never second-guesses the
target.

Fonte: `src/hooks/useAnimatedReorder.ts:ReorderExternalDrop`

<a id="animated-reorder-scope"></a>

### Reorder is direct manipulation of equal-sized rows or columns

The hook moves items by transform, not by re-render, and assumes every item
has the same size on the drag axis. Unequal sizes break the slot math the
preview is built on.

Fonte: `src/hooks/useAnimatedReorder.ts:useAnimatedReorder`

<a id="drag-resize-writes-dom"></a>

### Resize writes the DOM so React cannot fight the cursor

The pane width is set on the element directly during the drag. Routing it
through state would re-render mid-gesture and move the handle under the
pointer.

Fonte: `src/hooks/useDragResize.ts:useDragResize`

<a id="exit-animation-single-gate"></a>

### One gate decides whether enter/exit motion runs

The experimental-animations flag minus `prefers-reduced-motion` is the single
gate. Callers check this instead of reading either input, so a new surface
cannot animate while the user asked for none.

Fonte: `src/hooks/useExitAnimation.ts:useExperimentalAnimations`

<a id="exit-animation-delayed-unmount"></a>

### The owner keeps rendering until the outro finishes

While `closing` is true the owner still renders with a `*-closing` class, and
the real unmount runs from `onAnimationEnd` on the animated element. A timeout
fallback covers engines where `animationend` never fires (WebKitGTK) and the
reduced-motion path where animations are `none`; with the flag off the close is
immediate.

Fonte: `src/hooks/useExitAnimation.ts:useExitAnimation`

<a id="sortable-drop-contract"></a>

### A refused drop flags the target instead of acting

`allowed: false` means the drop is refused: the target is flagged, nothing is
moved. `onActivate` fires once the pointer crosses the drag threshold, before
any drop decision, so activation and refusal are separate moments.

Fonte: `src/hooks/useSortable.ts:SortableOptions`

<a id="branches-settled-means-looked"></a>

### `settled` means git answered, not that it is a repo

`settled` is true once the first lookup for the cwd finished, repo or not.
Callers that must not confuse "still looking" with "not a repo" read the flag
plus the list, never the list alone.

Fonte: `src/hooks/useProjectBranches.ts:useProjectBranchesState`

<a id="diff-pane-pushes-title-stats"></a>

### The fuller index pushes stats so the badge cannot lag

Stats from the diff pane's fuller git index are pushed to the title-bar badge
instead of letting the badge re-derive them. Two derivations would disagree
while the index is still loading.

Fonte: `src/hooks/useProjectDiffStats.ts:applyProjectDiffStats`

<a id="overscroll-edge-predicates"></a>

### Edge and room are directional predicates

`atScrollEdge` is true when the scroller has run out of room in the direction
the wheel is pushing; `hasScrollRoom` is the same question from the other side.
Both take the delta, so a diagonal gesture is judged per axis.

Fonte: `src/hooks/useLockOverscroll.ts:atScrollEdge`

<a id="overscroll-inner-takes-gesture"></a>

### An inner scroller with room keeps the gesture

A scroller between the wheel's target and the locked element that can still
take the gesture wins: cancelling at the outer level would cancel it there too,
since the browser picks what to scroll only after dispatch finishes.

Fonte: `src/hooks/useLockOverscroll.ts:innerScrollerTakes`

<a id="overscroll-standalone-for-tests"></a>

### The lock stands alone from the hook

`lockOverscroll` returns its detach and takes only the element, so the behavior
is exercised directly without mounting React.

Fonte: `src/hooks/useLockOverscroll.ts:lockOverscroll`

<a id="gesture-hook-test-strategy"></a>

### Gesture tests render the real hook under a browser check

The probe renders the real hook to get its gesture interface without mocking
React, and mount/unmount effects plus native click targeting run only behind a
browser check. Undisplaced tabs assert the negative: no transform and no
transition, keeping them out of the compositor.

Fonte: `src/hooks/useAnimatedReorder.test.ts:workspace tab gestures`

<a id="overscroll-test-fixtures"></a>

### Layout sizes are declared because happy-dom reports none

happy-dom reports 0 for every layout box, so the fixture declares the sizes.
The room test pins the scenario, not just the predicate: a pinned transcript
with a part-scrolled field in it, where cancelling the wheel would cancel the
field's own scrolling too, and the lock taking over once the field bottoms out.

Fonte: `src/hooks/useLockOverscroll.test.ts:useLockOverscroll wheel guard`

<a id="open-run-plans-then-applies"></a>

### A folder run is planned first, applied in selection order

Opening several folders plans the whole run from refs and commits it in one
transition in selection order. Planning per folder from refs would read state
React has not rendered yet. At most one folder takes the blank session under
`onCwdChange`'s own retargeting rules, tabs sit beside the previous one so the
order survives, the folder chosen last ends up focused, and the `reuse-blank`
arm is empty because `onCwdChange` already moved there.

Fonte: `src/app/useProjects.ts:openProjects`

<a id="picker-dismiss-keeps-views"></a>

### Dismissing the picker must not close open views

The early return comes after the dismiss handling, not before it.
`pickFolders` hands back an empty list on dismiss, which reads the same as
every path failing `looksLikeProject` — so closing the list first meant
cancelling a folder picker shut whatever the user had open. Nothing below
opens a project without also leaving one of these views.

Fonte: `src/app/useProjects.ts:useProjects`

<a id="same-run-tab-direct-focus"></a>

### A tab born in this run is focused directly

`setTabs` only schedules, so `tabsRef.current` does not have the fresh tab yet
and routing through `activateTab` would miss — leaving the previous pane
focused and the composer unfocused. The run focuses it directly, like the
create case one arm up. The planner's create-then-activate pair for a repeated
path is the same race from the other side.

Fonte: `src/app/useProjects.ts:useProjects`

<a id="dirty-preview-pins-itself"></a>

### A dirty preview pins itself before anything can replace it

An edited preview tab must not be replaced by the next click, so the dirty
flag pins the file first and records second. The order is the fix: recording
first would leave a window where a click swaps the edited buffer away.

Fonte: `src/app/useProjects.ts:useProjects`

<a id="unmount-reports-zero-errors"></a>

### Closed tabs drop out on the editor's unmount report

The editor reports 0 errors as it unmounts, so closed tabs clear themselves
without a separate close path. No report means the tab is gone, not clean.

Fonte: `src/app/useProjects.ts:useProjects`


<a id="pointer-capture-release-tolerated"></a>

### A released pointer capture is not an error

`releasePointerCapture` throws when the capture is already gone, which is a
normal end to a gesture, not a failure. Both call sites swallow it; an empty
`catch` without the pointer would read as a mistake, so the pointer stays.

Fonte: `src/hooks/useDragResize.ts:useDragResize`
12 changes: 12 additions & 0 deletions docs/notes/inbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,15 @@ Fonte: `src-tauri/src/inbox_media.rs:github_auth_token`
The inbox filter menu writes the same list, so follow it while both are mounted.

Fonte: `src/surfaces/settings/pages/inbox/LinearSettings.tsx:LinearSettings`

<a id="inbox-shared-refresh-plus-wrapper"></a>

### One refresh feeds the badge and the linked sessions

A single background refresh supplies both the Inbox badge and the linked
sessions: subscription wiring plus badge selectors in the hook, poll timing,
backoff, and fallback lookups in `../lib/inboxPoll`. The badge-only export is
a compatibility wrapper for consumers that never render sessions, not a second
subscription.

Fonte: `src/hooks/useInboxUnseen.ts:useInboxActivity`
52 changes: 52 additions & 0 deletions docs/notes/sessions-tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -951,3 +951,55 @@ Opt-in heuristic: pull shell-made file changes into this session's
review card. Adopted entries are never exact nor undoable.

Fonte: `src/app/workspaceEvents.ts:scheduleNudge`

<a id="workspace-tabs-ownership-map"></a>

### Tab layout owns the tree and the panes, not the sessions

`useWorkspaceTabs` owns tab tree state and pane placement: groups, drag,
close. Pure helpers live in `tabHelpers`; session data lives in
`useSessionSync`. Follow-up work splits into `useTabGroups`, `useTabDrag`,
`useTabClose`.

Fonte: `src/app/useWorkspaceTabs.ts:useWorkspaceTabs`

<a id="worktree-ref-wiring"></a>

### History deletion reaches worktrees through a ref set after mount

The worktree hooks object is set after mount and read at call time, so history
deletion consumes it through a ref rather than an import: the dependency runs
the other way, and a direct import would cycle.

Fonte: `src/app/useHistory.ts:WorktreeDeletionHooks`

<a id="nudge-coalescing-shape"></a>

### Nudges coalesce on the trailing edge

Edit-heavy turns emit several tool events, so nudges coalesce trailing-edge:
one timer, one flush, carrying every cwd that queued while it waited.

Fonte: `src/app/workspaceEvents.ts:scheduleNudge`

<a id="turn-status-folds-into-blocks"></a>

### Turn status folds into blocks; the meter hides without fresh data

While compacting, the level is unknown: the session reads busy, the meter is
cleared, the status is announced, and the start status folds straight into the
transcript blocks instead of travelling as an enqueued harness event. With no
fresh reading the stale pre-compaction level stays hidden until the next turn
reports — showing it would present a number the turn already invalidated.

Fonte: `src/app/useTurnActions.test.ts:manual context compaction`

<a id="all-changes-one-review"></a>

### Every working-tree change stacks into one review

Opening all changes stacks the whole working tree into a single review card
regardless of the diff-view setting. The setting chooses how a file reads, not
whether it is reviewed.

Fonte: `src/app/useWorkspaceTabs.ts:useWorkspaceTabs`
Loading
Loading