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
7 changes: 3 additions & 4 deletions apps/desktop/src/renderer/app-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2685,10 +2685,9 @@ function AppShellContent({
/>
) : null}
<ChatSurfaceLayout
// Stay mounted across session switches. Remounting would drop the
// Composer draft Map (drafts are not hosted outside this tree).
// Stock ChatLayout has no conversationKey; accept that scroll /
// new-message state is owned by content swaps, not a hard remount.
// Reset conversation-owned scroll state without remounting the
// composer: its contenteditable DOM carries the live draft.
conversationKey={activeId}
hidden={navSelection.section !== 'sessions'}
composer={
<>
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/quote-companion-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export function QuoteCompanionPanel(props: {
data-preparing={companion.preparing || undefined}
>
<ChatSurfaceLayout
key={companion.companionSession?.id ?? props.sourceSession?.id ?? 'companion'}
conversationKey={companion.companionSession?.id ?? props.sourceSession?.id}
composer={
<>
{companion.error && <div className="maka-quote-companion-error">{companion.error}</div>}
Expand Down
23 changes: 20 additions & 3 deletions packages/code-mode/src/__tests__/code-mode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,15 @@ test('runs nested tools concurrently inside one cell', async () => {
const calls: Array<{ name: string; input: unknown }> = [];
let active = 0;
let maxActive = 0;
const result = await execute(
let releaseTools!: () => void;
let observeConcurrency!: () => void;
const toolsReleased = new Promise<void>((resolve) => {
releaseTools = resolve;
});
const concurrencyObserved = new Promise<void>((resolve) => {
observeConcurrency = resolve;
});
const execution = execute(
`return await Promise.all([
tools.lookup({ id: 'a' }),
tools.lookup({ id: 'b' }),
Expand All @@ -81,13 +89,22 @@ test('runs nested tools concurrently inside one cell', async () => {
calls.push({ name, input });
active += 1;
maxActive = Math.max(maxActive, active);
await new Promise((resolve) => setImmediate(resolve));
if (active === 2) observeConcurrency();
await toolsReleased;
active -= 1;
return input;
},
},
);

const overlapped = await Promise.race([
concurrencyObserved.then(() => true),
execution.then(() => false),
]);
assert.equal(overlapped, true);
releaseTools();
const result = await execution;

assert.equal(maxActive, 2);
assert.deepEqual(calls, [
{ name: 'lookup', input: { id: 'a' } },
Expand Down Expand Up @@ -317,7 +334,7 @@ test('enforces byte and bridge limits', async (t) => {
},
});
assert.equal(result.ok ? undefined : result.error.kind, 'limit_exceeded');
assert.equal(started, 1);
assert.ok(started <= 1);
});
});

Expand Down
134 changes: 132 additions & 2 deletions patches/@astryxdesign+core+0.3.0.patch
Original file line number Diff line number Diff line change
@@ -1,5 +1,103 @@
diff --git a/node_modules/@astryxdesign/core/dist/Chat/ChatLayout.d.ts b/node_modules/@astryxdesign/core/dist/Chat/ChatLayout.d.ts
index ff34874..0a15599 100644
--- a/node_modules/@astryxdesign/core/dist/Chat/ChatLayout.d.ts
+++ b/node_modules/@astryxdesign/core/dist/Chat/ChatLayout.d.ts
@@ -69,6 +69,11 @@ export interface ChatLayoutProps extends BaseProps<HTMLDivElement> {
* @default 'balanced'
*/
density?: Density;
+ /**
+ * Per-conversation identity for hosts that switch conversations in place.
+ * Resets scroll and unread state without remounting composer content.
+ */
+ conversationKey?: string | number;
}
export declare function ChatLayout({ children, composer, density, emptyState, scrollButton, scrollRef: externalScrollRef, xstyle, className, style, 'data-testid': testId, ref, ...rest }: ChatLayoutProps): import("react").JSX.Element;
export declare namespace ChatLayout {
diff --git a/node_modules/@astryxdesign/core/dist/Chat/ChatLayout.js b/node_modules/@astryxdesign/core/dist/Chat/ChatLayout.js
index ea27cd5..856556c 100644
--- a/node_modules/@astryxdesign/core/dist/Chat/ChatLayout.js
+++ b/node_modules/@astryxdesign/core/dist/Chat/ChatLayout.js
@@ -28,7 +28,7 @@
* - /apps/storybook/stories/ChatLayout.stories.tsx
* - /packages/cli/assets/templates/blocks/components/ChatLayout/ (block examples)
*/
-import { useMemo, useRef } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
import * as stylex from '@stylexjs/stylex';
import "../theme/tokens.stylex.js";
import { spacingVars } from "../theme/tokens.stylex.js";
@@ -195,6 +195,7 @@ export function ChatLayout({
className,
style,
'data-testid': testId,
+ conversationKey,
ref,
...rest
}) {
@@ -211,6 +212,20 @@ export function ChatLayout({
isLocked: scroll.isLocked,
onResize: scroll.scrollIfLocked
});
+ useEffect(() => {
+ if (scroll.isLocked) {
+ newMsgs.dismiss();
+ }
+ }, [scroll.isLocked, newMsgs.dismiss]);
+ const conversationKeyRef = useRef(conversationKey);
+ useEffect(() => {
+ if (conversationKey === conversationKeyRef.current) {
+ return;
+ }
+ conversationKeyRef.current = conversationKey;
+ scroll.lock();
+ newMsgs.reset();
+ }, [conversationKey, scroll.lock, newMsgs.reset]);
const defaultScrollButton = /*#__PURE__*/_jsx(ChatLayoutScrollButton, {
isVisible: scroll.isScrolledUp || newMsgs.hasNewMessages,
label: newMsgs.hasNewMessages ? t('@astryx.chatLayout.newMessages') : undefined,
diff --git a/node_modules/@astryxdesign/core/dist/Chat/ChatToolCalls.js b/node_modules/@astryxdesign/core/dist/Chat/ChatToolCalls.js
index 791909e..9d4aa32 100644
--- a/node_modules/@astryxdesign/core/dist/Chat/ChatToolCalls.js
+++ b/node_modules/@astryxdesign/core/dist/Chat/ChatToolCalls.js
@@ -114,6 +114,7 @@ function CallRow({
const row = /*#__PURE__*/_jsxs("div", {
role: hasDetail ? 'button' : undefined,
tabIndex: hasDetail ? 0 : undefined,
+ "data-slot": "chat-tool-call-row",
"aria-expanded": hasDetail ? isDetailOpen : undefined,
"aria-controls": hasDetail && isDetailOpen ? detailId : undefined,
onClick: toggleDetail,
diff --git a/node_modules/@astryxdesign/core/dist/Chat/useChatNewMessages.js b/node_modules/@astryxdesign/core/dist/Chat/useChatNewMessages.js
index 8c509bc..e86d673 100644
--- a/node_modules/@astryxdesign/core/dist/Chat/useChatNewMessages.js
+++ b/node_modules/@astryxdesign/core/dist/Chat/useChatNewMessages.js
@@ -49,6 +49,8 @@ export function useChatNewMessages({
const elementRef = useRef(null);
const cleanupRef = useRef(null);
const attach = useCallback(el => {
+ lastMessageRef.current = null;
+ setHasNewMessages(false);
observeResize(el, () => {
onResizeRef.current?.();
const messages = el.getElementsByClassName('astryx-chat-message');
@@ -90,9 +92,14 @@ export function useChatNewMessages({
const dismiss = useCallback(() => {
setHasNewMessages(false);
}, []);
+ const reset = useCallback(() => {
+ lastMessageRef.current = null;
+ setHasNewMessages(false);
+ }, []);
return {
hasNewMessages,
dismiss,
+ reset,
contentRef
};
}
diff --git a/node_modules/@astryxdesign/core/dist/Kbd/Kbd.js b/node_modules/@astryxdesign/core/dist/Kbd/Kbd.js
index 33ed78d..0ecbce6 100644
index 33ed78d..44c6ad0 100644
--- a/node_modules/@astryxdesign/core/dist/Kbd/Kbd.js
+++ b/node_modules/@astryxdesign/core/dist/Kbd/Kbd.js
@@ -111,7 +111,10 @@ function detectMac() {
Expand All @@ -14,8 +112,28 @@ index 33ed78d..0ecbce6 100644
}
// Fallback: navigator.platform (deprecated but still shipped everywhere)
return /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? '');
diff --git a/node_modules/@astryxdesign/core/dist/List/List.js b/node_modules/@astryxdesign/core/dist/List/List.js
index 08b5f7d..5fc3c63 100644
--- a/node_modules/@astryxdesign/core/dist/List/List.js
+++ b/node_modules/@astryxdesign/core/dist/List/List.js
@@ -89,6 +89,7 @@ export function List({
className,
style,
'data-testid': testId,
+ 'aria-label': ariaLabel,
ref
}) {
const headerId = useId();
@@ -102,6 +103,7 @@ export function List({
const listElement = /*#__PURE__*/_jsx(Tag, {
ref: ref,
"data-testid": testId,
+ "aria-label": ariaLabel,
"aria-labelledby": header != null ? headerId : undefined,
...(isOrdered && start != null && start !== 1 ? {
start
diff --git a/node_modules/@astryxdesign/core/dist/hooks/useHotkeys.js b/node_modules/@astryxdesign/core/dist/hooks/useHotkeys.js
index 65f9278..f7515e2 100644
index 65f9278..56f6021 100644
--- a/node_modules/@astryxdesign/core/dist/hooks/useHotkeys.js
+++ b/node_modules/@astryxdesign/core/dist/hooks/useHotkeys.js
@@ -54,7 +54,10 @@ function isApplePlatform() {
Expand All @@ -30,3 +148,15 @@ index 65f9278..f7515e2 100644
}
return /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? '');
}
diff --git a/node_modules/@astryxdesign/core/src/Chat/ChatToolCalls.tsx b/node_modules/@astryxdesign/core/src/Chat/ChatToolCalls.tsx
index 234a2e4..e3f1842 100644
--- a/node_modules/@astryxdesign/core/src/Chat/ChatToolCalls.tsx
+++ b/node_modules/@astryxdesign/core/src/Chat/ChatToolCalls.tsx
@@ -378,6 +378,7 @@ function CallRow({call}: {call: ChatToolCallItem}) {
<div
role={hasDetail ? 'button' : undefined}
tabIndex={hasDetail ? 0 : undefined}
+ data-slot="chat-tool-call-row"
aria-expanded={hasDetail ? isDetailOpen : undefined}
aria-controls={hasDetail && isDetailOpen ? detailId : undefined}
onClick={toggleDetail}
13 changes: 9 additions & 4 deletions patches/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@ Delete when that guard passes against an unpatched package.

## `@astryxdesign/core@0.3.0`

Blank UA-CH `navigator.userAgentData.platform` must not mean "not Apple".
Three published component seams drop host-owned state or semantics:

- `ChatLayout` needs a conversation identity that resets scroll/unread state
without remounting its composer slot and discarding the live draft.
- `ChatToolCalls` needs a stable row slot for product styling and E2E geometry.
- `List` must forward its published `aria-label` to the rendered list element.

Blank UA-CH `navigator.userAgentData.platform` must also not mean "not Apple".
Electron builds with a rewritten identity ship `platform: ''`, which made every
`mod` hotkey listen for Ctrl and every `Kbd` draw Ctrl on macOS.

Touches only `Kbd` and `useHotkeys` platform probes.

Delete when Astryx treats a blank UA-CH platform as absent.
Delete each hunk when the corresponding behavior ships in Astryx.
Loading