Skip to content

Fix Runtime v2 runner and flow contracts - #6

Merged
likun666661 merged 1 commit into
runtime-v2-architecture-docfrom
runtime-v2-contract-fixes
Jun 14, 2026
Merged

Fix Runtime v2 runner and flow contracts#6
likun666661 merged 1 commit into
runtime-v2-architecture-docfrom
runtime-v2-contract-fixes

Conversation

@likun666661

Copy link
Copy Markdown
Member

Summary

  • make RuntimeRunner dispatch AgentFlow-compatible FlowInput with defined context and preserved attachments
  • fail RuntimeRunner deterministically when a flow exhausts without a terminal RuntimeEvent
  • make AiSdkFlow emit exactly one terminal event and stop yielding after the first terminal fact
  • preserve user attachment refs in RuntimeEvent text content and legacy adapters
  • prevent partial RuntimeEvents from becoming durable StoredMessage drafts

Verification

  • npm --workspace @maka/core run typecheck
  • npm --workspace @maka/runtime run typecheck
  • npm --workspace @maka/core run test (614/614)
  • npm --workspace @maka/runtime run test (394/394)
  • git diff --check
  • git diff --cached --check

Follow-up fix for review findings from PR #5.

@likun666661
likun666661 merged commit 6dd2ff8 into runtime-v2-architecture-doc Jun 14, 2026
@likun666661
likun666661 deleted the runtime-v2-contract-fixes branch June 14, 2026 15:13
jackwener pushed a commit that referenced this pull request Jun 21, 2026
Fix Runtime v2 runner and flow contracts
jackwener added a commit that referenced this pull request Jun 21, 2026
Earlier §9.1.5 #4 promised that iframe `<a href>` clicks would route
through PR96 `setWindowOpenHandler` → `shell.openExternal`. That was
documentation incorrect — per @kenji's review: without `allow-popups`
in the sandbox attribute, iframe link clicks don't bubble up to the
parent renderer's window-open handler. They silently fail or get
blocked by the sandbox.

Replaces #4 with the right MVP shape:
- iframe sandbox blocks ALL navigation by default — keep that as the
  contract
- preview top status bar shows "此预览中已禁用外部链接 · {N} 个链接"
  (count of <a href> in srcdoc), so users aren't confused why
  clicks do nothing
- Users who need to follow links use "在 Finder 中打开" from the
  artifact toolbar and open the source file in their browser

Future enhancements (NOT PR108b):
- HTML transform: rewrite <a href> → <button data-maka-link> + inline
  postMessage script, main renderer is the only shell.openExternal
  gateway
- Or: postMessage allowlist for iframe ↔ main renderer

Safety-first MVP: HTML can be VIEWED, not NAVIGATED. Cheap UX
sacrifice, clear trust boundary.
jackwener added a commit that referenced this pull request Jun 21, 2026
…ound 25)

Rounds 1-3 never traced workspaces; round-3 01 saw .alma/skills
as a workspace-scoped path but didn't follow it. This note covers
the table, default resolution cascade, thread binding, and
project-scoped surfaces.

Schema (main.js:257-276): workspaces with isTemporary +
showInList + isWorktree + parentWorkspaceId + worktreeBranch +
autoWorktree + autoWorktreeBaseBranch + prNumber/url/state/base.
Two surprises: worktrees are workspace rows too (with parent
backref), and PR metadata is baked into the workspace row.

Default-workspace resolution (4-layer cascade, replicated at 6
call sites including main.js:19128-19144, 42586, 29033, 43217,
62591, 19138):
1. Per-channel override: settings[platform].channelWorkspaceMap[channelId]
2. User default: settings.general.defaultWorkspaceId
3. Builtin: getOrCreateDefaultWorkspace at main.js:4909-4928
   creates {userData}/workspaces/default
4. Implicit fallthrough if id lookup fails

Threads carry TWO workspace FKs (main.js:286-291):
- workspaceId: session workspace (Bash cwd, Read default, skills)
- artifactWorkspaceId: chat-generated artifacts (lazily created
  as 'Artifacts - thread_title' workspace at main.js:3757-3773)
Both ON DELETE SET NULL — orphan threads survive, tool calls
bail at guard.

Thread reassignment pattern in bot/heartbeat paths (main.js:
34867, 38303, 40676, 42280, 43055): when long-lived thread
recovers, sync it to current default workspace, not the one it
was bound to. Right for bot/cron threads; would be wrong for
code-coupled threads. All call sites are in bot/heartbeat
handlers — verified scoped.

Project-scoped surfaces use workspace.path as prefix:
- <workspace>/.alma/skills (round-3 01 skill loader root #6)
- <workspace>/.alma/agents (cross-ref round-2 07)
- workspacePath passed to Bash session cwd

isInsideSkillDirectory (main.js:18622) treats project-root
.alma/skills equivalently to ~/.config/alma/skills for safety
checks.

ACP/Bash sessions keyed on (workspaceId:threadId) at
main.js:48110-48150. killSessionsForWorkspace iterates and
nukes all matching. workspacePath spawned-process cwd.

Worktree mode: autoWorktree:true tells alma to fork a git
worktree per task. Creates child workspace row pointing to
parent. Connects to round-1/-2 Task tool isolation:"worktree"
flag — autoWorktree is the runtime expression.

Open question (flagged): does alma cleanup unused autoWorktree
workspaces? Without cleanup, list grows monotonically.

5 ranked Maka improvements:
1. Promote workspace to first-class DB table
2. artifactWorkspaceId pattern (prevents tree pollution)
3. Default-workspace cascade (2-layer is enough for Maka today)
4. isInsideSkillDirectory safety check when skills land
5. Per-thread workspace binding (avoids cross-project confusion)

4 open questions: worktree lifecycle / bot reassignment opt-out /
channelWorkspaceMap collision / PR metadata refresh.

Updates 00-OVERVIEW.md inventory.

Non-conflicting with xuan (Settings) + kenji (non-Settings UI):
notes/ only.
jackwener pushed a commit that referenced this pull request Jun 21, 2026
Fix Runtime v2 runner and flow contracts
jackwener added a commit that referenced this pull request Jun 21, 2026
Earlier §9.1.5 #4 promised that iframe `<a href>` clicks would route
through PR96 `setWindowOpenHandler` → `shell.openExternal`. That was
documentation incorrect — per @kenji's review: without `allow-popups`
in the sandbox attribute, iframe link clicks don't bubble up to the
parent renderer's window-open handler. They silently fail or get
blocked by the sandbox.

Replaces #4 with the right MVP shape:
- iframe sandbox blocks ALL navigation by default — keep that as the
  contract
- preview top status bar shows "此预览中已禁用外部链接 · {N} 个链接"
  (count of <a href> in srcdoc), so users aren't confused why
  clicks do nothing
- Users who need to follow links use "在 Finder 中打开" from the
  artifact toolbar and open the source file in their browser

Future enhancements (NOT PR108b):
- HTML transform: rewrite <a href> → <button data-maka-link> + inline
  postMessage script, main renderer is the only shell.openExternal
  gateway
- Or: postMessage allowlist for iframe ↔ main renderer

Safety-first MVP: HTML can be VIEWED, not NAVIGATED. Cheap UX
sacrifice, clear trust boundary.
jackwener added a commit that referenced this pull request Jun 21, 2026
…ound 25)

Rounds 1-3 never traced workspaces; round-3 01 saw .alma/skills
as a workspace-scoped path but didn't follow it. This note covers
the table, default resolution cascade, thread binding, and
project-scoped surfaces.

Schema (main.js:257-276): workspaces with isTemporary +
showInList + isWorktree + parentWorkspaceId + worktreeBranch +
autoWorktree + autoWorktreeBaseBranch + prNumber/url/state/base.
Two surprises: worktrees are workspace rows too (with parent
backref), and PR metadata is baked into the workspace row.

Default-workspace resolution (4-layer cascade, replicated at 6
call sites including main.js:19128-19144, 42586, 29033, 43217,
62591, 19138):
1. Per-channel override: settings[platform].channelWorkspaceMap[channelId]
2. User default: settings.general.defaultWorkspaceId
3. Builtin: getOrCreateDefaultWorkspace at main.js:4909-4928
   creates {userData}/workspaces/default
4. Implicit fallthrough if id lookup fails

Threads carry TWO workspace FKs (main.js:286-291):
- workspaceId: session workspace (Bash cwd, Read default, skills)
- artifactWorkspaceId: chat-generated artifacts (lazily created
  as 'Artifacts - thread_title' workspace at main.js:3757-3773)
Both ON DELETE SET NULL — orphan threads survive, tool calls
bail at guard.

Thread reassignment pattern in bot/heartbeat paths (main.js:
34867, 38303, 40676, 42280, 43055): when long-lived thread
recovers, sync it to current default workspace, not the one it
was bound to. Right for bot/cron threads; would be wrong for
code-coupled threads. All call sites are in bot/heartbeat
handlers — verified scoped.

Project-scoped surfaces use workspace.path as prefix:
- <workspace>/.alma/skills (round-3 01 skill loader root #6)
- <workspace>/.alma/agents (cross-ref round-2 07)
- workspacePath passed to Bash session cwd

isInsideSkillDirectory (main.js:18622) treats project-root
.alma/skills equivalently to ~/.config/alma/skills for safety
checks.

ACP/Bash sessions keyed on (workspaceId:threadId) at
main.js:48110-48150. killSessionsForWorkspace iterates and
nukes all matching. workspacePath spawned-process cwd.

Worktree mode: autoWorktree:true tells alma to fork a git
worktree per task. Creates child workspace row pointing to
parent. Connects to round-1/-2 Task tool isolation:"worktree"
flag — autoWorktree is the runtime expression.

Open question (flagged): does alma cleanup unused autoWorktree
workspaces? Without cleanup, list grows monotonically.

5 ranked Maka improvements:
1. Promote workspace to first-class DB table
2. artifactWorkspaceId pattern (prevents tree pollution)
3. Default-workspace cascade (2-layer is enough for Maka today)
4. isInsideSkillDirectory safety check when skills land
5. Per-thread workspace binding (avoids cross-project confusion)

4 open questions: worktree lifecycle / bot reassignment opt-out /
channelWorkspaceMap collision / PR metadata refresh.

Updates 00-OVERVIEW.md inventory.

Non-conflicting with xuan (Settings) + kenji (non-Settings UI):
notes/ only.
jackwener pushed a commit that referenced this pull request Jun 21, 2026
Fix Runtime v2 runner and flow contracts
jackwener added a commit that referenced this pull request Jun 24, 2026
…ji audit #3+#4) (#219)

* test(contract): lock drawer.tsx + tabs.tsx motion escape hatches (kenji audit #3 + #4)

Sibling of PR-FE-BUG-HUNT-12 (which locked ui.tsx). Closes kenji's
audit reminder 4-6 findings #3 + #4 (msg `6cc0e04d` 2026-06-24):

## drawer.tsx escape hatches (5)

- `cubic-bezier(0.32,0.72,0,1)` ×2 — iOS-style settle curve, raw on
  backdrop + popup. Should move to `--ease-drawer` token.
- `duration-450` ×2 — drawer settle duration. Doesn't match any
  current `--duration-*` token. Should tokenize.
- `transition-[transform,box-shadow,height,background-color]` ×1 —
  animating `height` is layout-trigger. Intentional because drawer
  snap points (peek / half / full) drive variable height and
  `transform: scaleY` would distort children. Layout-property
  transition is acknowledged here.
- `backdrop-blur-sm` ×1 — same finding as kenji #6.
- `z-50` ×1 — same convention as ui.tsx; pending tokenization.

## tabs.tsx escape hatches (3)

- `transition-[width,translate]` ×1 — animating `width` is layout-
  trigger. Cleaner refactor is `translate + scaleX` with measured
  base width, but that needs measurement infrastructure not in
  place. Layout transition acknowledged.
- `duration-200` ×1 — matches `--duration-base` (200ms) by value
  but uses bare Tailwind utility.
- `ease-in-out` ×1 — generic Tailwind easing, not project's
  `--ease-out-strong`.

## Approach

Same as PR-FE-BUG-HUNT-12 (ui.tsx contract): pin EXACT count of
each allowlisted pattern. Adding new sites OR removing stale
allowlist entries both fail. Plus a "no other layout transitions"
sweep that verifies no NEW `transition-[<bracketed-list>]` showed
up beyond the allowlisted ones.

Not touching source — touches primitive wrappers; each tokenization
needs kenji / WAWQAQ review per primitive. Contract locks perimeter.

## Diff

1 new file, ~140 lines test. No source change, no visual change.
No overlap with PR-FE-BUG-HUNT-12 (different file scope) or any
other open PR.

## Verification

Disk still ~100%, couldn't run tests. Greenfield contract is
self-validating — first run confirms counts match current file
state. The allowlist counts were derived from grep + manual read.

* test(self-review): fix wrong occurrence counts in primitives contract

Verified counts via `grep -o ... | wc -l` before pushing PR #219 to
review and caught my own miscounts:

- drawer.tsx `z-50`: 1 → actual 3 (gesture wrapper, backdrop scrim,
  cushion overlay all stack at z-50)
- drawer.tsx had TWO additional `transition-[<bracketed>]` patterns
  I missed: `transition-[transform,box-shadow,height,background-color,
  margin,padding]` (drawer popup bottom-edge — worse layout trigger
  than the base, adds margin+padding) and `transition-[background-
  color,box-shadow]` (drawer-internal switch handle — paint-only,
  safe)
- tabs.tsx had ONE additional `transition-[color,background-color,
  box-shadow]` pattern (tabs trigger paint transition — paint-only,
  safe)

All five additions added to the allowlist with explicit reason. The
`no unexpected transition-[<bracketed>]` sweep rewritten to subtract
the full allowlist from the found set (set-difference) instead of
naively iterating, so it doesn't false-fail when there are multiple
paint-only patterns.

Pre-push count verification is now baked into the test contract
itself — if a future maintainer changes a count without re-running,
the test will fail loudly.
jackwener added a commit that referenced this pull request Jun 25, 2026
Per @kenji audit `26a221be` against #237 main `4e0247f6`:

**#1 — bottom 「想先看看效果?」 hero buttons left-aligned.**
PR #236 right-aligned the top 「打开每日回顾」 button via
`.settingsFeatureStatusHeroActions` but the bottom 「生成每日回顾 /
生成深度分析」 row still used inline `style={{ display: 'flex', gap:
8, marginTop: 8 }}`, so it stayed glued to the paragraph margin.
Route the bottom row through the same class. Add `gap: 8px` to the
class so multi-button clusters keep proper spacing.

**#2 — 执行时间 disabled when the master switch is off.**
The disable condition was `formDisabled || savingKey === 'executeTime'
|| !(effectiveConfig?.enabled ?? false)`. That last clause blocked the
common UX of "pick a time first, then turn it on". Drop it — the time
value is harmless to edit while disabled (it doesn't fire until the
switch flips), and the disabled state was misread as "broken UI".

**#3 — 分析模型 default-row label still verbose.**
WAWQAQ's directive: "所有模型选择只用模型名". PR #236 went from
`使用对话默认模型(Codex OAuth · email · gpt-5.5)` to
`对话默认(gpt-5.5)` — better, but the model id parenthetical still
mixes "selectable model" with "follow chat default". Per @kenji's
"don't conflate the two semantics", the default row becomes plain
`跟随对话默认`; the explicit options carry the model id.

**#4 — duplicate model ids across connections looked unselectable.**
When two enabled connections both expose `gpt-5.5`, the flat
`model`-only labels read as two identical entries. Detect the collision
at build time and append `· ${connection.name}` ONLY to the colliding
entries; unique model ids stay terse per WAWQAQ's directive.

Deferred (with @kenji concurrence):
- **#5** (`data-control-width` semantic API replacing CSS-selector width
  patches) — pure refactor, no end-result change. WAWQAQ explicitly
  said 「只看最后的结果」.
- **#6** (collapse Daily Review hero card pile into a single grouped
  settings surface) — bigger visual restructure judgment call; needs
  WAWQAQ sign-off before swinging at the whole page layout.
jackwener added a commit that referenced this pull request Jun 25, 2026
…#244)

Bundled response to @kenji audit findings #5 + #6 (msg `26a221be`)
plus the page-restructure WAWQAQ explicitly authorized
(thread msg `afbe542d`: 「一次性多改,别他妈分这么多轮」).

**Finding #6 — Daily Review settings page restructure.**
Removed the two `.settingsFeatureStatusHero` cards (intro + 「想先看看
效果?」). They were promo chrome on a workbench settings surface and
duplicated information the section banner already carries. The
「打开每日回顾」 / 「生成每日回顾」 / 「生成深度分析」 actions are now
folded into a single right-aligned `.settingsPageFooterActions`
toolbar rendered below the rows. Net: one banner, one grouped rows
surface, one action footer — no card pile.

The `.settingsFeatureStatusHero*` CSS rules stay because the voice
settings page still uses them; this PR just drops Daily Review's
dependency on them.

**Finding #5 — semantic width attr instead of class-selector patches.**
`.settingsRow > .settingsTimeInput` and
`.settingsRow > .settingsBaseSelectTrigger` were globally-scoped
selectors that implicitly assumed the row contained one specific
control type. Move the contract to a `data-control-width="compact|
select"` attribute on the row itself; mark the 执行时间 and 分析模型
rows accordingly. The old global rules are gone — only the data-attr
rules survive.

Verified the 4 other `SettingsSelect` call sites (proxy host, gateway
host, bot domain, usage filter) are inside `.settingsField`, not
`.settingsRow`, so they aren't affected by the dropped rule.
jackwener added a commit that referenced this pull request Jun 25, 2026
@kenji audit thread `#my-ai:4821a792` msg `e4cfbfb0` vs main `71d88454`.
WAWQAQ msg `782a1663` + `继续`: bundle these, don't split.

**#1 — Memory page top control rows had no outer card surface.**
The three control rows (本地 MEMORY.md / 模型上下文可读取 / 项目指令
文件) were direct children of `.settingsStructuredPage`, which is
transparent. `.settingsFormRow` only carries a hairline bottom border,
so users saw three floating rows without the grouped-card visual every
other settings surface uses. Wrap them in `.settingsRows` so they
inherit the 12px outer border + radius + overflow:hidden treatment.

**#2 — Plan Reminder delivery picker showed text-only IM channels.**
Settings → 机器人对话 switched to real brand logos (Iconify simple-icons)
in #BOT-SETTINGS-UI-0, but the chat-side Plan Reminder delivery flow's
`平台` select was still rendering plain Chinese labels. Same channels
should read the same everywhere.
- Move `BOT_BRAND` to `packages/ui/src/bot-brand.ts` so both surfaces
  share a single brand-metadata source instead of one duplicating the
  other.
- Extend `PlanReminderSelect` to accept a third tuple slot (optional
  `ReactNode` icon); `PlanReminderPanel` now passes an `<IconifyIcon
  icon={brand.iconifyId}>` per provider with the existing offline
  glyph fallback.
- Update `SettingsModal.tsx` to import `BOT_BRAND` from `@maka/ui`
  instead of declaring a local copy.

**#5 — Two inline `style={{ marginTop }}` spacing patches.**
- `Alert variant="error" style={{ marginTop: 12 }}` → `className=
  "settingsSurfaceAlert"`.
- `<div className="settingsActionRow" style={{ marginTop: 8 }}>` →
  `className="settingsActionRow settingsNoticeAction"`.
The new classes pin the spacing decision in CSS where the design
tokens live, instead of inline JSX.

Deferred per kenji's own scope note + WAWQAQ 「只看最后的结果」:
- **#3** (`RadioCard` / `ChoiceCard` primitive for theme/palette picker)
  — pure design-system refactor, no visible delta on Settings → 外观.
- **#4** (`SettingsSelect` uplift to rich-option contract) — bigger
  cross-cutting refactor; current fix covers the immediate model-name
  and disambiguation issues kenji raised in round 1.
- **#6** (layout-property transitions in sidebar/tabs/accordion) —
  kenji explicitly flagged these as visual-smoke-gated; will land
  alongside the smoke config in a separate PR.
jackwener added a commit that referenced this pull request Jun 25, 2026
… dvh (#250)

@kenji audit msg `232aec0f` (`#my-ai:c28a6293`). Round 3 against
`af66ebb7` post-#249.

**#2 — Plan Reminder select collapsed state was text-only.**
PR #247 added a brand icon to each `<SelectItem>`, but `<SelectValue
/>` defaulted to rendering just the label string — open dropdown
showed a logo, collapsed trigger showed plain text. Build a
`value → { label, icon }` lookup inside `PlanReminderSelect` and pass
a function-child to `SelectValue` so the picked state renders the same
icon + label row the dropdown items render.

**#6 — `100vh` residue in renderer CSS.**
Four sites still on `100vh`:
- `maka-tokens.css:760` `.maka-shell` `height`
- `maka-tokens.css:1941` `.maka-modal` `max-height: calc(100vh - 80px)`
- `styles.css:5028` `.maka-help-modal` `max-height: calc(100vh - 96px)`
- `styles.css:13121` `.maka-onboarding-stack` `min-height: calc(100vh - 84px)`

Replace each with `100dvh` so Electron browser frames with dynamic
chrome (or a future mobile-style viewport) don't get half a viewport
of height calculation drift. Other renderer surfaces already use
`100dvh`; this brings the legacy four into line.

Deferred per kenji's own scope:
- **#1** feishu/dingtalk official-brand-kit sourcing — separate PR.
- **#3 / #4** SettingsSelect / Segmented / ChoiceCard primitive
  unification — design-system refactor; no end-result delta on its
  own.
- **#5** layout-property transitions in sidebar/tabs/accordion —
  kenji explicitly gated on visual smoke harness.
jackwener pushed a commit that referenced this pull request Jul 6, 2026
…ete (#520 PR8) (#562)

* feat(ui): migrate SearchModal result list to Base UI Autocomplete (#520 PR8)

SearchModal's hand-rolled roving-focus result list (activeResultIndex /
moveActiveResult / jumpActiveResult / keyboardSelectionHandledRef /
handleResultKeyDown / data-active) is replaced by Base UI Autocomplete in
activedescendant mode:

- Autocomplete.Root inline + mode="none" + autoHighlight="always" +
  filter={null}: the list renders inline in the modal body (no floating
  popup), Autocomplete does not re-filter the server-side IPC results, and
  the first result is always highlighted so Enter works without an extra
  ArrowDown.
- Autocomplete.Input renders the input via the shared InputGroupInput
  primitive (render prop); ArrowUp/Down/Enter/Escape keyboard nav is owned
  by Autocomplete (floating-ui useListNavigation).
- Autocomplete.List + Autocomplete.Item replace the hand-rolled
  <ul role=listbox> + <li><button role=option>; item onClick fires
  selectResult for both pointer click and Enter on the highlighted item.
- aria-activedescendant on the input is now managed by Autocomplete.

selectResult navigation (sessionId + turnId, restoreFocus: false), the
debounced IPC search, the inflight ticket guard, the unmount invalidation,
the clear button, the snippet rendering, and all copy/states are unchanged.

The roving-focus kbd-nav interaction (ArrowDown moved focus to the result
button) becomes activedescendant (input keeps focus, active item reflected
via aria-activedescendant). a11y is more standard; the interaction habit
shifts. Home/End now move the input cursor (Base UI ComboboxInput default);
jump-to-first/last result is not bound for now (to be confirmed by manual
testing per PR8 plan).

search-modal-lifecycle-contract: the kbd-nav it-block is rewritten to lock
the Autocomplete shape (Root props + Item onClick + selectResult navigation)
instead of the roving-focus implementation; the focus-policy it-block drops
the activeResultIndex/moveActiveResult assertions; the empty-query it-block
repoints onChange -> onValueChange. CSS .maka-search-modal-result[data-active]
-> [data-highlighted] (Autocomplete item highlighted state).

Verification: typecheck clean, @maka/desktop 2076/2076, @maka/ui 43/43,
sidebar-search-modal-open screenshot AE=2655 (fuzz 5%, RMSE 0.0006) vs main.

* feat(ui): migrate CommandPalette to Base UI Autocomplete (#520 PR8)

CommandPalette's hand-rolled activedescendant result list (highlight
state + onInputKeyDown + reset useEffect + <div role=listbox> +
<Button role=option data-active>) is replaced by Base UI Autocomplete:

- Autocomplete.Root inline + mode="none" + autoHighlight="always" +
  filter={null}: the list renders inline in the modal body, Autocomplete
  does not re-filter the palette's own fuzzy + content-search combined
  list, and the first command is always highlighted so Enter works
  without an extra ArrowDown.
- Autocomplete.Input renders the input via the shared InputGroupInput
  primitive (render prop); ArrowUp/Down/Enter/Escape keyboard nav is
  owned by Autocomplete. aria-controls + aria-activedescendant are
  managed by Autocomplete — no manual wiring.
- Autocomplete.List + Autocomplete.Group + Autocomplete.GroupLabel +
  Autocomplete.Item replace the hand-rolled <div role=listbox> +
  <div group> + <Button role=option>. Autocomplete.Item fires onClick
  for both pointer click and Enter on the highlighted item, so commit()
  covers both paths.
- The CornerDownLeft cursor hint is now CSS-driven
  (.maka-palette-cursor visibility via [data-highlighted]) instead of
  the JS `!cmd.hint \&\& active` conditional, since the hand-rolled
  highlight state is gone.

commit() (commitPendingRef + committedCommandId + await run + finally
close), the fuzzy filter, useThreadSearch content-search, grouped
rendering, and all copy/states are unchanged.

The kbd-nav interaction stays activedescendant (input keeps focus,
active item reflected via aria-activedescendant) — same mode as before,
now owned by Autocomplete. Home/End now move the input cursor (Base UI
ComboboxInput default); jump-to-first/last command is not bound for now
(to be confirmed by manual testing per PR8 plan).

Contracts: command-palette-a11y-copy-contract #1 (listbox) rewritten to
lock the Autocomplete shape; #2 import regex drops Button (no longer
used); #3 CSS data-active -> data-highlighted; #6 commit-gate block
boundary is commit() (onInputKeyDown gone); #7 highlight reset is now
autoHighlight="always" (no hand-rolled state). renderer-utility-primitives
row assert repoints <Button role=option> -> <Autocomplete.Item>. CSS
.maka-palette-item[data-active] -> [data-highlighted] + .maka-palette-cursor.

Verification: typecheck clean, @maka/desktop 2076/2076, @maka/ui 43/43,
command-palette-open screenshot AE=7250 (fuzz 5%, RMSE 0.0017) vs main.

* fix(ui): Autocomplete inline open + item-press guard (#562 review)

P1 (blocker): both Autocomplete.Root used `inline` without `open`. Per
Base UI docs, `inline` requires `open` so the list is treated as visible:
"Specify open unconditionally in conjunction with this prop so the list is
considered visible: <Autocomplete.Root inline open>". Without `open`,
defaultOpen=false -> the input is not data-popup-open and keyboard nav /
activedescendant do not work. Add `open` to SearchModal + CommandPalette.

P2-a: object items (<Autocomplete.Item value={result/cmd}>) had no
itemToStringValue, and onValueChange did not filter item-press. Add
itemToStringValue (result.title / cmd.label) so item-press never writes
[object Object] into the query, and skip onValueChange when
details.reason === 'item-press'. In inline mode selectionMode='none' + no
Popup means shouldFillInput is currently false (popupRef.current null), so
this is defensive — but correct regardless of future Popup changes.

P2-b: contract tests now lock open + itemToStringValue + onValueChange
item-press-filter on Autocomplete.Root (catches P1/P2-a regressions), plus
the existing inline/mode/autoHighlight/List/Item shape.

Verified via CDP probe (command-palette-open fixture): input has
data-popup-open + aria-controls + aria-activedescendant; first item
data-highlighted (autoHighlight); ArrowDown 0->1->2, ArrowUp 2->1.
typecheck clean, @maka/ui 43/43, @maka/desktop 2076/2076.
command-palette-open AE=48000 (RMSE 0.011) vs main — larger than pre-fix
AE=7250 because open now correctly renders the first-item highlight bg +
input data-popup-open state (the bug state hid these). search-modal-open
AE=2655 (RMSE 0.0006, unchanged).

* test(ui): lock Home/End input-cursor decision (#562 P2-c)

P2-c decision: accept Base UI ComboboxInput's default — Home/End move the
input cursor, not the highlight. The old roving-focus jumpActiveResult
(SearchModal) / hand-rolled onInputKeyDown highlight jump (CommandPalette)
must not return. Lock via doesNotMatch on jumpActive( / onInputKeyDown.

* fix(ui): keepHighlight + empty-state inside Autocomplete.List (#562 review P2)

P2-1 (keepHighlight): both Roots had autoHighlight="always" but no
keepHighlight. keepHighlight=false (default) sets resetOnPointerLeave=true
(AriaCombobox.js:855), so pointer leave clears activeIndex, then the
autoHighlight="always" effect (line 678-682) re-highlights the first item
-> hover item[2] -> leave -> Enter ran the first item, not the hovered one.
Add keepHighlight so pointer leave preserves the hovered item.

P2-2 (empty-state): CommandPalette rendered a standalone <div> for empty
and <Autocomplete.List> only when non-empty, so the input lost its listbox
reference with no matches. Unify on Autocomplete.List always, with the Empty
primitive inside. Autocomplete.Empty is not used: filter={null} + mode="none"
keeps filteredItems non-empty (the palette's fuzzy filter is external), so
Autocomplete.Empty would never trigger.

Contracts lock keepHighlight on both Roots + empty-state-must-not-use-
standalone-div.

keepHighlight verified by source (resetOnPointerLeave = !keepHighlight) +
Base UI docs. CDP hover-leave probe could not reliably trigger floating-ui
useListNavigation's hover highlight (synthesized pointermove does not fire
its hover detection), so pointer-leave preservation is source-guaranteed,
not probe-verified. typecheck clean, 2076/2076, screenshots unchanged
(command-palette-open AE=48000, search-modal-open AE=2655 vs main — same as
prior commit, keepHighlight/empty-state don't affect non-empty visuals).
anaconda110 added a commit to anaconda110/maka-agent that referenced this pull request Aug 3, 2026
…ngs.gradle (P1 apache#6)

The settings.gradle script was calling applyNativeModulesAppBuildGradle(project),
which belongs in app/build.gradle (already called there at line 134). Per the RN
autolinking contract (see @react-native-community/cli native_modules.gradle:
ext.applyNativeModulesSettingsGradle = { DefaultSettings defaultSettings -> ... }
ext.applyNativeModulesAppBuildGradle = { Project project -> ... }), the settings
phase must call applyNativeModulesSettingsGradle(settings) to register autolinked
native modules as Gradle projects, while the app phase wires their implementations
into :app. Aligns with .hive/android-code-review.md P1 apache#6.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant