Merge remote-tracking branch 'origin/main' into port-2619
# Conflicts: # packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx
This commit is contained in:
@@ -7,6 +7,16 @@ everything between typing and sending.
|
||||
own state and wires these modules together; it should not grow logic that
|
||||
belongs to one of them.
|
||||
|
||||
`ChatContainer.tsx` keeps one `ChatInput` mounted while a new-session draft
|
||||
becomes its first session. Draft-only UI first fades for 120ms while the editor
|
||||
stays in place. The parent then moves the editor to its final session position
|
||||
with a 180ms transform-only FLIP animation. Reduced-motion mode skips these
|
||||
transitions. `session-ui-store.ts` marks sessions materialized from a submitted
|
||||
draft, so selecting an existing session while a draft is open switches without
|
||||
animation. Do not restore separate draft and session composer branches:
|
||||
remounting the editor loses focus and interrupts the transition. Keep the
|
||||
existing mobile fixed-position rules unchanged.
|
||||
|
||||
## Layers
|
||||
|
||||
| Directory | Owns |
|
||||
@@ -62,6 +72,15 @@ copy.
|
||||
exactly what gets sent, so nothing downstream serializes a rich document model
|
||||
back into a prompt.
|
||||
|
||||
The document is not, however, the string it was given: CodeMirror normalizes
|
||||
line endings, so a `\r\n` pair becomes one break and the document ends up
|
||||
shorter than the inserted string. **Never derive a caret position from the
|
||||
length of text you are inserting** — a caret past the end makes `dispatch`
|
||||
throw, the transaction never applies, and the un-normalized text stays in React
|
||||
state to crash again on the next restore. Every edit that moves the caret goes
|
||||
through `replaceWithCaret` (`editor/documentEdits.ts`), which measures the
|
||||
change instead of the string.
|
||||
|
||||
The composer previously painted a transparent `<textarea>` over a mirror
|
||||
`<div>`. That restricted highlighting to styles which do not change glyph
|
||||
advance width — colour, background, underline — because anything else made the
|
||||
@@ -73,20 +92,54 @@ question of design, not of feasibility.
|
||||
Selection rendering: every device runs CodeMirror's `drawSelection()` — it
|
||||
keeps typing on the drawn-selection code path, and removing it makes
|
||||
CodeMirror enforce cursor association on the native selection, which iOS
|
||||
answers with severe input lag. Every device also layers
|
||||
`composerNativeSelectionExtension` (`editor/theme.ts`) on top: it re-shows
|
||||
answers with severe input lag. **That much is not platform-specific and must
|
||||
not be undone.** What differs is who paints the selection, and
|
||||
`composerSelectionExtension` (`editor/theme.ts`) picks that per platform.
|
||||
|
||||
When CodeMirror 6.43.9's iOS predicate does not match,
|
||||
`composerNativeSelectionExtension` layers over `drawSelection()`: it re-shows
|
||||
the native selection, and — only while a range is selected — the native caret,
|
||||
hiding the painted layers those replace. The native selection is the one that
|
||||
shows for two reasons: the painted layer sits behind the content, so tokens
|
||||
with their own background (inline code, fences) cover it completely; and
|
||||
iOS's selection drag handles attach to the visible native selection and take
|
||||
their colour from the caret, so a transparent caret means invisible handles.
|
||||
The range-only caret scoping is load-bearing — a native caret visible while
|
||||
typing makes WebKit re-render its caret UI after every keystroke, felt as
|
||||
severe input lag. The selection tint comes from `--primary`, not the selection
|
||||
token:
|
||||
themes define `--interactive-selection` with its own alpha, so a translucent
|
||||
mix of it is nearly invisible.
|
||||
with their own background (inline code, fences) cover it completely; and the
|
||||
platform's selection drag handles attach to the visible native selection and
|
||||
take their colour from the caret, so a transparent caret means invisible
|
||||
handles. The range-only caret scoping is load-bearing — a native caret visible
|
||||
while typing makes the browser re-render its caret UI after every keystroke,
|
||||
felt as severe input lag.
|
||||
|
||||
When CodeMirror 6.43.9's exact iOS predicate matches,
|
||||
`composerIOSSelectionExtension` leaves selection-handle geometry and appearance
|
||||
to CodeMirror. CodeMirror puts the handles in `.cm-selectionLayer`, normally at
|
||||
`z-index: -1`; the extension raises that layer above the content so opaque
|
||||
token backgrounds cannot cover them, and leaves it transparent to touch.
|
||||
The handle dots extend 8px past their range; matching scroller padding and
|
||||
negative margin expand the clip area without moving the text or changing the
|
||||
composer height. iOS still paints its taller system selection overlay even
|
||||
when CSS makes `::selection` transparent. The extension therefore suppresses
|
||||
CodeMirror's synthetic selection rectangles on iOS while leaving its handles,
|
||||
cursor path and `nativeSelectionHidden` facet active. Otherwise the grey system
|
||||
highlight and themed rectangle overlap with visibly different heights.
|
||||
Do not add a second custom layer or custom handles here: overlapping translucent
|
||||
rectangles make selection darker at their seams and imitated handles drift from
|
||||
the geometry WebKit actually manipulates. What iOS avoids is installing the
|
||||
native-selection workaround above: explicitly restoring native paint and caret
|
||||
makes WebKit re-measure them after every decoration redraw, and the composer
|
||||
rebuilds every decoration on every keystroke. That cost is felt worst during
|
||||
IME composition.
|
||||
|
||||
The non-iOS native selection tint comes from `--primary`, not the selection
|
||||
token: themes define `--interactive-selection` with its own alpha, so mixing it
|
||||
with transparent again is nearly invisible. The iOS system overlay owns its
|
||||
visible selection fill.
|
||||
|
||||
The content element keeps the existing correction policy: on in the mobile UI,
|
||||
off elsewhere. CodeMirror also reads the attribute and reverts Apple and
|
||||
Android's insert-period-on-double-space only when its value is exactly `off`.
|
||||
`editor/autocorrect.ts` uses the HTML standard's
|
||||
[ASCII case-insensitive `autocorrect` keywords](https://html.spec.whatwg.org/multipage/interaction.html#attr-autocorrect)
|
||||
to keep desktop word correction off while avoiding that CodeMirror-only
|
||||
revert. Its platform checks deliberately match CodeMirror's own browser flags.
|
||||
|
||||
`composerLanguage.ts` retokenizes the whole document on every change. The
|
||||
composer holds a prompt, not a source file: it is short enough that a full pass
|
||||
@@ -100,10 +153,14 @@ and the send path reading the same grammar.
|
||||
drawn caret through a class it only writes while applying an update, so the
|
||||
selection has to be the update that follows the focus.
|
||||
- `submit/buildOutgoingMessage.ts` flattens queued messages, the composer text,
|
||||
inline comments and context into OpenCode's one-primary-plus-parts shape. The
|
||||
oldest queued message becomes primary; **inline comments attach to the last
|
||||
body the user authored** rather than becoming their own part; PR instructions
|
||||
precede the PR diff.
|
||||
context drafts and linked references into OpenCode's one-primary-plus-parts
|
||||
shape. The oldest queued message becomes primary. **Every attached context
|
||||
item (inline comments, terminal selections, browser annotations, PR context,
|
||||
linked issue/PR) becomes its own synthetic text part carrying structured
|
||||
metadata** built by `lib/messages/contextParts.ts`; the timeline reads that
|
||||
metadata back to render context blocks. PR instructions precede the PR diff.
|
||||
Queueing a message leaves context drafts in their store on purpose — the send
|
||||
that later delivers the queue consumes them.
|
||||
- `state/useComposerDraft.ts` — a draft belongs to a (runtime, directory,
|
||||
session) identity. Writes are debounced while typing but forced at every edge
|
||||
where the page may stop running, because a pending timer is not a saved
|
||||
@@ -113,6 +170,9 @@ and the send path reading the same grammar.
|
||||
- `state/useDraftTarget.ts` — the draft can target a directory that does not
|
||||
exist yet (a worktree being created). It must survive not appearing in the
|
||||
branch list, or the selector snaps back to the project root mid-creation.
|
||||
- `ui/DraftTargetSelectors.tsx` owns the controlled project/worktree picker
|
||||
state and registers its application shortcuts locally. The selectors only
|
||||
consume their shared prefix while the draft target UI is mounted.
|
||||
|
||||
## Mobile
|
||||
|
||||
|
||||
Reference in New Issue
Block a user