@agent-native/core 0.149.4 → 0.149.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/content/actions/_document-discovery-query.ts +127 -0
- package/corpus/templates/content/actions/list-documents.ts +189 -198
- package/corpus/templates/content/actions/search-documents.ts +95 -24
- package/corpus/templates/content/app/hooks/use-documents.ts +84 -6
- package/corpus/templates/content/shared/api.ts +10 -0
- package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +5 -0
- package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +23 -4
- package/corpus/templates/slides/actions/get-deck.ts +25 -9
- package/corpus/templates/slides/actions/patch-deck.ts +51 -0
- package/corpus/templates/slides/actions/update-slide.ts +144 -14
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +12 -4
- package/corpus/templates/slides/changelog/2026-08-11-pdf-requests-to-restyle-and-preserve-a-source-deck-now-impor.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-slide-edits-preserve-their-layout-without-introducing-hidden.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-slides-reliably-imports-uploaded-pdfs-and-presentations.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-slides-support-atomic-code-style-html-patches-without-regene.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/server/lib/slide-content-patch.ts +355 -0
- package/corpus/templates/slides/server/lib/uploaded-reference-storage.ts +28 -9
- package/corpus/templates/slides/server/plugins/agent-chat.ts +12 -3
- package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +5 -0
- package/dist/cli/create.js +49 -0
- package/dist/client/chat-first/index.d.ts +1 -1
- package/dist/client/chat-first/index.js +1 -1
- package/dist/client/chat-first/primary-nav.d.ts +3 -1
- package/dist/client/chat-first/primary-nav.js +8 -2
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/private-blob/registry.js +67 -4
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -0
- package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +5 -0
- package/dist/templates/default/pnpm-workspace.yaml +44 -0
- package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -0
- package/dist/templates/workspace-root/pnpm-workspace.yaml +44 -0
- package/package.json +1 -1
- package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -0
- package/src/templates/default/.agents/skills/frontend-design/SKILL.md +5 -0
- package/src/templates/default/pnpm-workspace.yaml +44 -0
- package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -0
- package/src/templates/workspace-root/pnpm-workspace.yaml +44 -0
|
@@ -84,25 +84,44 @@ export async function readUploadedReferenceBlob(
|
|
|
84
84
|
decryptSecretValue(reference.slice(UPLOADED_REFERENCE_PREFIX.length)),
|
|
85
85
|
) as UploadedReferenceDescriptor;
|
|
86
86
|
} catch {
|
|
87
|
+
console.warn("[slides-upload] invalid uploaded reference descriptor", {
|
|
88
|
+
reason: "decrypt-or-parse",
|
|
89
|
+
});
|
|
87
90
|
throw new Error("Invalid uploaded file reference");
|
|
88
91
|
}
|
|
89
92
|
|
|
93
|
+
const requestOrgId = getRequestOrgId() ?? null;
|
|
94
|
+
const ownerMatches = descriptor?.ownerKey === tenantFileKey(email);
|
|
95
|
+
const orgMatches = descriptor?.orgId === requestOrgId;
|
|
96
|
+
const filename = descriptor?.filename;
|
|
97
|
+
const safeFilename =
|
|
98
|
+
typeof filename === "string" && filename === path.basename(filename)
|
|
99
|
+
? filename
|
|
100
|
+
: null;
|
|
101
|
+
const handleIsOpaque = descriptor?.handle?.opaque === true;
|
|
90
102
|
if (
|
|
91
103
|
descriptor?.kind !== "slides-upload" ||
|
|
92
|
-
descriptor
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
descriptor
|
|
97
|
-
typeof descriptor
|
|
98
|
-
|
|
99
|
-
descriptor.handle.opaque !== true
|
|
104
|
+
descriptor?.version !== 1 ||
|
|
105
|
+
!ownerMatches ||
|
|
106
|
+
!orgMatches ||
|
|
107
|
+
safeFilename === null ||
|
|
108
|
+
typeof descriptor?.handle?.id !== "string" ||
|
|
109
|
+
typeof descriptor?.handle?.provider !== "string" ||
|
|
110
|
+
!handleIsOpaque
|
|
100
111
|
) {
|
|
112
|
+
console.warn("[slides-upload] uploaded reference rejected", {
|
|
113
|
+
handleIsOpaque,
|
|
114
|
+
hasDescriptorOrg: descriptor?.orgId != null,
|
|
115
|
+
hasRequestOrg: requestOrgId != null,
|
|
116
|
+
orgMatches,
|
|
117
|
+
ownerMatches,
|
|
118
|
+
reason: "scope-or-handle-validation",
|
|
119
|
+
});
|
|
101
120
|
throw new Error(
|
|
102
121
|
"Access denied: uploaded file reference is not valid for this user or organization",
|
|
103
122
|
);
|
|
104
123
|
}
|
|
105
124
|
|
|
106
125
|
const blob = await readPrivateBlob(descriptor.handle);
|
|
107
|
-
return { data: Buffer.from(blob.data), filename:
|
|
126
|
+
return { data: Buffer.from(blob.data), filename: safeFilename };
|
|
108
127
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { getOrgContext } from "@agent-native/core/org";
|
|
2
1
|
import {
|
|
3
2
|
createAgentChatPlugin,
|
|
4
3
|
loadActionsFromStaticRegistry,
|
|
5
4
|
} from "@agent-native/core/server";
|
|
6
5
|
|
|
7
6
|
import actionsRegistry from "../../.generated/actions-registry.js";
|
|
7
|
+
import { resolveSlidesRequestAuthContext } from "../handlers/request-auth-context.js";
|
|
8
8
|
import { prepareSlidesChatAttachments } from "../lib/chat-attachments.js";
|
|
9
9
|
import "../register-secrets.js";
|
|
10
10
|
|
|
@@ -48,12 +48,21 @@ export default createAgentChatPlugin({
|
|
|
48
48
|
// paginate, and reduce provider data through providerFetch() without us
|
|
49
49
|
// hardcoding one action per Google Drive endpoint.
|
|
50
50
|
codeExecution: { production: "sandboxed" },
|
|
51
|
-
|
|
51
|
+
// Upload routes and action routes must use the same session/org resolver.
|
|
52
|
+
// Reading getOrgContext directly here skipped the upload route's session
|
|
53
|
+
// fallback and could reject a freshly uploaded reference after a transient
|
|
54
|
+
// org lookup or active-org transition.
|
|
55
|
+
resolveOrgId: async (event) => {
|
|
56
|
+
const authContext = await resolveSlidesRequestAuthContext(event);
|
|
57
|
+
return authContext.orgId === undefined ? null : authContext.orgId;
|
|
58
|
+
},
|
|
52
59
|
prepareRequest: prepareSlidesChatAttachments,
|
|
53
60
|
systemPrompt: `You are an AI deck assistant. You create, edit, import, export, style, share, and navigate decks through actions and shared application state. For a newly created presentation, use create-deck with slides: [] only when you are creating the deck yourself, then add-slide sequentially with full rendered HTML. The legacy generate-slides-ai action returns Markdown drafts and is not part of the persisted presentation workflow.
|
|
54
61
|
|
|
55
62
|
When the user asks to improve, beautify, restyle, or make an uploaded/existing deck on-brand, treat it as an in-place source-preserving edit unless the user explicitly asks to rewrite the story or change slide count. First call view-screen when the active deck is unclear, then get-deck with compact=true for deck orientation. If you need slide markup, call get-deck with compact=false explicitly and only when the edit requires the full HTML. If get-deck.sourceImport exists, preserve its slide count, order, IDs, factual copy, notes, images, charts, tables, diagrams, freeform objects, and source aspect ratio. For a deck-wide restyle, use one patch-deck call with requireAllSourceSlides=true and one patch-slide operation with fields.content for every source slide ID; the action rejects partial coverage. Do not split a full-deck restyle into arbitrary batches or use one-by-one update-slide calls - reserve update-slide for targeted one-slide edits. After the patch succeeds, verify with get-deck using compact=true so the verification does not retransmit every slide's HTML. Do not claim a partial or initial pass. Do not use add-slide, delete, reorder, or replace source imagery with generic cards for this workflow. If sourceImport.fidelity is partial or imagesSkipped is nonzero, stop and report the exact fidelity warning instead of claiming a reliable improvement.
|
|
56
|
-
For a targeted one-slide edit, prefer get-deck with the stable slideId from view-screen or compact get-deck. It returns only that slide's full HTML by default, so do not load the full deck when one slide is enough. Pass compact=true when you only need a lightweight check of that slide.
|
|
63
|
+
For a targeted one-slide edit, prefer get-deck with the stable slideId from view-screen or compact get-deck. It returns only that slide's full HTML by default, so do not load the full deck when one slide is enough. For code-style edits, request compact=false and format=true, then call update-slide with that slide's contentHash as baseContentHash and an ordered edits list. Use exact replace, insert-before/after, replace-between, or regex-replace edits; include expectedMatches for ambiguous markers. The action applies the whole list atomically under the deck lock, so a failed required match writes nothing. Set format=true on update-slide when readable line breaks should be persisted. Use fullContent only for an intentional full rewrite. Pass compact=true when you only need a lightweight check of that slide.
|
|
64
|
+
|
|
65
|
+
For click-to-reveal animations, keep the slide HTML as the visual source and store reveals only in the slide's animations metadata. Read the target slide's full HTML, preserve its existing structure, and patch the complete ordered animations list with elementPath values from that final HTML. Leave labels and headings visible by omitting them from the list. Never simulate reveals by adding duplicate elements, visibility:hidden, fmd-layout-spacer, fmd-freeform-object, absolute positioning, transforms, or placeholder markup. If content and reveals both change, send them together in one patch-deck patch-slide operation. If a user asks to remove or revert reveals, send the existing content with animations: [], then re-read the slide; do not use a simplified replacement or rely on update-slide text alone.
|
|
57
66
|
|
|
58
67
|
When the active Slides editor is already showing the deck you just changed, do not include an "Open the updated deck" or similar link in the final response. The deck is already open and the action updated it in place; say that plainly instead. Only provide an open-deck link when the user is elsewhere, the active deck is different, or the user explicitly asks for a link.
|
|
59
68
|
|
|
@@ -168,6 +168,11 @@ the useful information remains dense and scannable.
|
|
|
168
168
|
- Do not build custom dropdowns, menus, popovers, modals, or confirmations with manual absolute positioning and click-outside effects.
|
|
169
169
|
- Never use browser dialogs (`window.alert`, `window.confirm`, `window.prompt`). Use `AlertDialog`, `Dialog`, or app-specific confirmation UI.
|
|
170
170
|
- Use Tabler icons for all first-party UI icons. Do not add Lucide, Heroicons, inline SVG icon sets, or emoji icons.
|
|
171
|
+
- Never guess a Tabler icon name. Names like `IconPartyPopper` or `IconConfettiCannon` feel
|
|
172
|
+
plausible but don't exist and crash Vite with a "Named export not found" error. Before importing
|
|
173
|
+
an icon you haven't used elsewhere in this app, confirm it exists by grepping
|
|
174
|
+
`node_modules/@tabler/icons-react/dist/tabler-icons-react.d.ts` (or the package's icon list)
|
|
175
|
+
for the exact name, and pick the closest real match if your first guess isn't there.
|
|
171
176
|
- Keep inline help/info glyphs next to labels at `size-3` (12px) or smaller than the adjacent text. Preserve a larger hit area on the trigger, not the glyph. Use `guard:allow-large-help-icon` only for deliberate heading documentation or menu action exceptions.
|
|
172
177
|
- Use `useActionQuery` and `useActionMutation` from `@agent-native/core/client` for action-backed UI. Standard CRUD should go through actions, not custom `/api/` routes.
|
|
173
178
|
- Keep UI optimistic where possible: update cache and navigation immediately, then reconcile or roll back on mutation result.
|
package/dist/cli/create.js
CHANGED
|
@@ -17,6 +17,49 @@ const STANDALONE_EXACT_DEPENDENCY_OVERRIDES = {
|
|
|
17
17
|
"@react-router/fs-routes": "8.1.0",
|
|
18
18
|
"react-router": "8.1.0",
|
|
19
19
|
};
|
|
20
|
+
const TIPTAP_WORKSPACE_OVERRIDES = {
|
|
21
|
+
'"@tiptap/core"': '"3.28.0"',
|
|
22
|
+
'"@tiptap/extension-blockquote"': '"3.28.0"',
|
|
23
|
+
'"@tiptap/extension-bold"': '"3.28.0"',
|
|
24
|
+
'"@tiptap/extension-bubble-menu"': '"3.28.0"',
|
|
25
|
+
'"@tiptap/extension-bullet-list"': '"3.28.0"',
|
|
26
|
+
'"@tiptap/extension-code"': '"3.28.0"',
|
|
27
|
+
'"@tiptap/extension-code-block"': '"3.28.0"',
|
|
28
|
+
'"@tiptap/extension-code-block-lowlight"': '"3.28.0"',
|
|
29
|
+
'"@tiptap/extension-collaboration"': '"3.28.0"',
|
|
30
|
+
'"@tiptap/extension-collaboration-caret"': '"3.28.0"',
|
|
31
|
+
'"@tiptap/extension-color"': '"3.28.0"',
|
|
32
|
+
'"@tiptap/extension-document"': '"3.28.0"',
|
|
33
|
+
'"@tiptap/extension-dropcursor"': '"3.28.0"',
|
|
34
|
+
'"@tiptap/extension-floating-menu"': '"3.28.0"',
|
|
35
|
+
'"@tiptap/extension-gapcursor"': '"3.28.0"',
|
|
36
|
+
'"@tiptap/extension-hard-break"': '"3.28.0"',
|
|
37
|
+
'"@tiptap/extension-heading"': '"3.28.0"',
|
|
38
|
+
'"@tiptap/extension-horizontal-rule"': '"3.28.0"',
|
|
39
|
+
'"@tiptap/extension-image"': '"3.28.0"',
|
|
40
|
+
'"@tiptap/extension-italic"': '"3.28.0"',
|
|
41
|
+
'"@tiptap/extension-link"': '"3.28.0"',
|
|
42
|
+
'"@tiptap/extension-list"': '"3.28.0"',
|
|
43
|
+
'"@tiptap/extension-list-item"': '"3.28.0"',
|
|
44
|
+
'"@tiptap/extension-list-keymap"': '"3.28.0"',
|
|
45
|
+
'"@tiptap/extension-ordered-list"': '"3.28.0"',
|
|
46
|
+
'"@tiptap/extension-paragraph"': '"3.28.0"',
|
|
47
|
+
'"@tiptap/extension-placeholder"': '"3.28.0"',
|
|
48
|
+
'"@tiptap/extension-strike"': '"3.28.0"',
|
|
49
|
+
'"@tiptap/extension-table"': '"3.28.0"',
|
|
50
|
+
'"@tiptap/extension-table-cell"': '"3.28.0"',
|
|
51
|
+
'"@tiptap/extension-table-header"': '"3.28.0"',
|
|
52
|
+
'"@tiptap/extension-table-row"': '"3.28.0"',
|
|
53
|
+
'"@tiptap/extension-task-item"': '"3.28.0"',
|
|
54
|
+
'"@tiptap/extension-task-list"': '"3.28.0"',
|
|
55
|
+
'"@tiptap/extension-text"': '"3.28.0"',
|
|
56
|
+
'"@tiptap/extension-text-style"': '"3.28.0"',
|
|
57
|
+
'"@tiptap/extension-underline"': '"3.28.0"',
|
|
58
|
+
'"@tiptap/extensions"': '"3.28.0"',
|
|
59
|
+
'"@tiptap/pm"': '"3.28.0"',
|
|
60
|
+
'"@tiptap/react"': '"3.28.0"',
|
|
61
|
+
'"@tiptap/starter-kit"': '"3.28.0"',
|
|
62
|
+
};
|
|
20
63
|
const REACT_ROUTER_BUILD_DEPENDENCIES = [
|
|
21
64
|
"@react-router/dev",
|
|
22
65
|
"@react-router/fs-routes",
|
|
@@ -1457,6 +1500,12 @@ function postProcessStandalone(name, targetDir, templateName, resolution) {
|
|
|
1457
1500
|
nf3: '"0.3.17"',
|
|
1458
1501
|
};
|
|
1459
1502
|
}
|
|
1503
|
+
if (templateName && getTemplate(templateName)) {
|
|
1504
|
+
sections.overrides = {
|
|
1505
|
+
...sections.overrides,
|
|
1506
|
+
...TIPTAP_WORKSPACE_OVERRIDES,
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1460
1509
|
const localToolkit = localToolkitOverride();
|
|
1461
1510
|
if (localToolkit) {
|
|
1462
1511
|
sections.overrides ??= {};
|
|
@@ -6,7 +6,7 @@ export { defaultChatFirstCopy } from "./copy.js";
|
|
|
6
6
|
export { ChatFirstChatHistory, type ChatFirstChatHistoryProps, } from "./chat-history.js";
|
|
7
7
|
export { ChatFirstSessionWatchPane } from "./session-watch-pane.js";
|
|
8
8
|
export { ChatFirstSurfacePanel, type ChatFirstSurfacePanelProps, } from "./surface-panel.js";
|
|
9
|
-
export { ChatFirstPrimaryNavigation } from "./primary-nav.js";
|
|
9
|
+
export { ChatFirstPrimaryNavigation, type ChatFirstPrimaryTab, } from "./primary-nav.js";
|
|
10
10
|
export { emitChatFirstOpenApp, resolveChatFirstAppTarget, CHAT_FIRST_DEFAULT_APP_IDS, type ChatFirstOpenAppDetail, } from "../chat-first.js";
|
|
11
11
|
export { buildChatFirstAppCreationPrompt, titleFromChatFirstAppPrompt, type ChatFirstAppCreationPromptInput, type ChatFirstAppCreationResource, type ChatFirstAppCreationVaultAccessMode, } from "../../shared/chat-first-app-creation.js";
|
|
12
12
|
export { ChatFirstSurfaceContent, ChatFirstSurfaceTabs, } from "./surface-tabs.js";
|
|
@@ -6,7 +6,7 @@ export { defaultChatFirstCopy } from "./copy.js";
|
|
|
6
6
|
export { ChatFirstChatHistory, } from "./chat-history.js";
|
|
7
7
|
export { ChatFirstSessionWatchPane } from "./session-watch-pane.js";
|
|
8
8
|
export { ChatFirstSurfacePanel, } from "./surface-panel.js";
|
|
9
|
-
export { ChatFirstPrimaryNavigation } from "./primary-nav.js";
|
|
9
|
+
export { ChatFirstPrimaryNavigation, } from "./primary-nav.js";
|
|
10
10
|
export { emitChatFirstOpenApp, resolveChatFirstAppTarget, CHAT_FIRST_DEFAULT_APP_IDS, } from "../chat-first.js";
|
|
11
11
|
export { buildChatFirstAppCreationPrompt, titleFromChatFirstAppPrompt, } from "../../shared/chat-first-app-creation.js";
|
|
12
12
|
export { ChatFirstSurfaceContent, ChatFirstSurfaceTabs, } from "./surface-tabs.js";
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { ChatFirstCopy } from "./types.js";
|
|
2
|
-
export
|
|
2
|
+
export type ChatFirstPrimaryTab = "new-chat" | "integrations" | "scheduled";
|
|
3
|
+
export declare function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOpenScheduled, onSearch, activeTab, copy, }: {
|
|
3
4
|
onNewChat?: () => void;
|
|
4
5
|
onOpenIntegrations: () => void;
|
|
5
6
|
onOpenScheduled: () => void;
|
|
6
7
|
onSearch?: () => void;
|
|
8
|
+
activeTab?: ChatFirstPrimaryTab;
|
|
7
9
|
copy?: ChatFirstCopy;
|
|
8
10
|
}): import("react").JSX.Element;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { IconClock, IconPlus, IconPlugConnected, IconSearch, } from "@tabler/icons-react";
|
|
3
3
|
import { defaultChatFirstCopy } from "./copy.js";
|
|
4
|
-
export function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOpenScheduled, onSearch, copy = defaultChatFirstCopy, }) {
|
|
5
|
-
|
|
4
|
+
export function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOpenScheduled, onSearch, activeTab, copy = defaultChatFirstCopy, }) {
|
|
5
|
+
const tabClassName = (tab) => `flex h-8 w-full items-center gap-2 rounded-md px-2 text-[13px] font-medium transition-[background-color,color] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${activeTab === tab
|
|
6
|
+
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
|
7
|
+
: "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"}`;
|
|
8
|
+
const renderTab = (tab, content, onSelect) => (_jsx("button", { type: "button", role: "tab", "aria-selected": activeTab === tab, className: tabClassName(tab), onClick: onSelect, children: content }));
|
|
9
|
+
return (_jsxs("div", { children: [_jsxs("div", { role: "tablist", "aria-label": "Primary navigation", className: "grid gap-px", children: [onNewChat
|
|
10
|
+
? renderTab("new-chat", _jsxs(_Fragment, { children: [_jsx(IconPlus, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("newChat") })] }), onNewChat)
|
|
11
|
+
: null, renderTab("integrations", _jsxs(_Fragment, { children: [_jsx(IconPlugConnected, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("integrations") })] }), onOpenIntegrations), renderTab("scheduled", _jsxs(_Fragment, { children: [_jsx(IconClock, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("scheduled") })] }), onOpenScheduled)] }), onSearch ? (_jsxs("button", { type: "button", className: "mt-px flex h-8 w-full items-center gap-2 rounded-md px-2 text-[13px] font-medium text-sidebar-foreground/80 transition-[background-color,color] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: onSearch, children: [_jsx(IconSearch, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("search") })] })) : null] }));
|
|
6
12
|
}
|
|
@@ -137,13 +137,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
137
137
|
inputSchema: {
|
|
138
138
|
type: string;
|
|
139
139
|
properties: {
|
|
140
|
+
count?: undefined;
|
|
140
141
|
query?: undefined;
|
|
141
142
|
minutes?: undefined;
|
|
142
143
|
limit?: undefined;
|
|
143
144
|
clientHint?: undefined;
|
|
144
145
|
timestamp?: undefined;
|
|
145
146
|
chapterId?: undefined;
|
|
146
|
-
count?: undefined;
|
|
147
147
|
startAt?: undefined;
|
|
148
148
|
endAt?: undefined;
|
|
149
149
|
reason?: undefined;
|
|
@@ -159,6 +159,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
159
159
|
inputSchema: {
|
|
160
160
|
type: string;
|
|
161
161
|
properties: {
|
|
162
|
+
count?: undefined;
|
|
162
163
|
query: {
|
|
163
164
|
type: string;
|
|
164
165
|
description: string;
|
|
@@ -174,7 +175,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
174
175
|
clientHint?: undefined;
|
|
175
176
|
timestamp?: undefined;
|
|
176
177
|
chapterId?: undefined;
|
|
177
|
-
count?: undefined;
|
|
178
178
|
startAt?: undefined;
|
|
179
179
|
endAt?: undefined;
|
|
180
180
|
reason?: undefined;
|
|
@@ -190,6 +190,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
190
190
|
inputSchema: {
|
|
191
191
|
type: string;
|
|
192
192
|
properties: {
|
|
193
|
+
count?: undefined;
|
|
193
194
|
minutes: {
|
|
194
195
|
type: string;
|
|
195
196
|
description: string;
|
|
@@ -199,7 +200,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
199
200
|
clientHint?: undefined;
|
|
200
201
|
timestamp?: undefined;
|
|
201
202
|
chapterId?: undefined;
|
|
202
|
-
count?: undefined;
|
|
203
203
|
startAt?: undefined;
|
|
204
204
|
endAt?: undefined;
|
|
205
205
|
reason?: undefined;
|
|
@@ -216,6 +216,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
216
216
|
type: string;
|
|
217
217
|
required: string[];
|
|
218
218
|
properties: {
|
|
219
|
+
count?: undefined;
|
|
219
220
|
query: {
|
|
220
221
|
type: string;
|
|
221
222
|
description: string;
|
|
@@ -234,7 +235,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
234
235
|
};
|
|
235
236
|
timestamp?: undefined;
|
|
236
237
|
chapterId?: undefined;
|
|
237
|
-
count?: undefined;
|
|
238
238
|
startAt?: undefined;
|
|
239
239
|
endAt?: undefined;
|
|
240
240
|
reason?: undefined;
|
|
@@ -250,6 +250,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
250
250
|
type: string;
|
|
251
251
|
required: string[];
|
|
252
252
|
properties: {
|
|
253
|
+
count?: undefined;
|
|
253
254
|
query?: undefined;
|
|
254
255
|
minutes?: undefined;
|
|
255
256
|
limit?: undefined;
|
|
@@ -263,7 +264,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
263
264
|
description: string;
|
|
264
265
|
};
|
|
265
266
|
chapterId?: undefined;
|
|
266
|
-
count?: undefined;
|
|
267
267
|
startAt?: undefined;
|
|
268
268
|
endAt?: undefined;
|
|
269
269
|
includeMicrophone?: undefined;
|
|
@@ -314,13 +314,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
314
314
|
type: string;
|
|
315
315
|
required: string[];
|
|
316
316
|
properties: {
|
|
317
|
+
count?: undefined;
|
|
317
318
|
query?: undefined;
|
|
318
319
|
minutes?: undefined;
|
|
319
320
|
limit?: undefined;
|
|
320
321
|
clientHint?: undefined;
|
|
321
322
|
timestamp?: undefined;
|
|
322
323
|
chapterId?: undefined;
|
|
323
|
-
count?: undefined;
|
|
324
324
|
startAt: {
|
|
325
325
|
type: string;
|
|
326
326
|
description: string;
|
|
@@ -349,13 +349,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
349
349
|
type: string;
|
|
350
350
|
required: string[];
|
|
351
351
|
properties: {
|
|
352
|
+
count?: undefined;
|
|
352
353
|
query?: undefined;
|
|
353
354
|
minutes?: undefined;
|
|
354
355
|
limit?: undefined;
|
|
355
356
|
clientHint?: undefined;
|
|
356
357
|
timestamp?: undefined;
|
|
357
358
|
chapterId?: undefined;
|
|
358
|
-
count?: undefined;
|
|
359
359
|
startAt?: undefined;
|
|
360
360
|
endAt?: undefined;
|
|
361
361
|
reason?: undefined;
|
|
@@ -16,18 +16,18 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
|
|
|
16
16
|
error?: undefined;
|
|
17
17
|
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
|
+
count?: undefined;
|
|
19
20
|
updated: number;
|
|
20
21
|
error?: undefined;
|
|
21
22
|
ok?: undefined;
|
|
22
|
-
count?: undefined;
|
|
23
23
|
} | {
|
|
24
|
+
count?: undefined;
|
|
24
25
|
updated?: undefined;
|
|
25
26
|
error: string;
|
|
26
27
|
ok?: undefined;
|
|
27
|
-
count?: undefined;
|
|
28
28
|
} | {
|
|
29
|
+
count?: undefined;
|
|
29
30
|
updated?: undefined;
|
|
30
31
|
error?: undefined;
|
|
31
32
|
ok: boolean;
|
|
32
|
-
count?: undefined;
|
|
33
33
|
}>>;
|
|
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
-
error?: undefined;
|
|
45
44
|
summary: import("./types.js").TraceSummary;
|
|
46
45
|
spans: import("./types.js").TraceSpan[];
|
|
47
46
|
id?: undefined;
|
|
47
|
+
error?: undefined;
|
|
48
48
|
ok?: undefined;
|
|
49
49
|
} | {
|
|
50
|
-
error?: undefined;
|
|
51
50
|
summary?: undefined;
|
|
52
51
|
spans?: undefined;
|
|
53
52
|
id: string;
|
|
53
|
+
error?: undefined;
|
|
54
54
|
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
56
|
summary?: undefined;
|
|
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
59
59
|
error: any;
|
|
60
60
|
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
|
-
error?: undefined;
|
|
63
62
|
summary?: undefined;
|
|
64
63
|
spans?: undefined;
|
|
65
64
|
id?: undefined;
|
|
65
|
+
error?: undefined;
|
|
66
66
|
ok: boolean;
|
|
67
67
|
}>>;
|
|
@@ -2,6 +2,7 @@ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
|
2
2
|
import { uploadFile } from "../file-upload/index.js";
|
|
3
3
|
import { decryptSecretValue, encryptSecretValue, getSecretEncryptionKey, } from "../secrets/crypto.js";
|
|
4
4
|
const PUBLIC_UPLOAD_HANDLE_PREFIX = "public-upload:v1:";
|
|
5
|
+
const PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS = [100, 250, 500];
|
|
5
6
|
const globals = globalThis;
|
|
6
7
|
const providers = (globals.__agentNativePrivateBlobProviders ??= new Map());
|
|
7
8
|
const publicUploadFallbackRef = (globals.__agentNativePrivateBlobPublicUploadFallback ??= {
|
|
@@ -47,6 +48,16 @@ function decodePublicUploadDescriptor(id) {
|
|
|
47
48
|
function isPublicUploadFallbackHandle(handle) {
|
|
48
49
|
return handle.id.startsWith(PUBLIC_UPLOAD_HANDLE_PREFIX);
|
|
49
50
|
}
|
|
51
|
+
function isRetryablePublicUploadStatus(status) {
|
|
52
|
+
return (status === 404 ||
|
|
53
|
+
status === 408 ||
|
|
54
|
+
status === 425 ||
|
|
55
|
+
status === 429 ||
|
|
56
|
+
(status >= 500 && status <= 599));
|
|
57
|
+
}
|
|
58
|
+
function waitForPublicUploadRetry(delayMs) {
|
|
59
|
+
return new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
60
|
+
}
|
|
50
61
|
async function putViaEncryptedPublicUpload(input) {
|
|
51
62
|
const bytes = toBytes(input.data);
|
|
52
63
|
const encrypted = encryptBytes(bytes);
|
|
@@ -71,7 +82,7 @@ async function putViaEncryptedPublicUpload(input) {
|
|
|
71
82
|
size: bytes.byteLength,
|
|
72
83
|
createdAt: new Date().toISOString(),
|
|
73
84
|
};
|
|
74
|
-
|
|
85
|
+
const handle = {
|
|
75
86
|
id: encodePublicUploadDescriptor(descriptor),
|
|
76
87
|
provider: `public-upload:${uploaded.provider}`,
|
|
77
88
|
opaque: true,
|
|
@@ -81,12 +92,64 @@ async function putViaEncryptedPublicUpload(input) {
|
|
|
81
92
|
createdAt: descriptor.createdAt,
|
|
82
93
|
metadata: input.metadata,
|
|
83
94
|
};
|
|
95
|
+
// Do not hand callers a reference that the next request cannot read yet.
|
|
96
|
+
// The public-upload fallback is eventually consistent at the URL boundary,
|
|
97
|
+
// so readiness belongs to the write path as well as the later read path.
|
|
98
|
+
await readViaEncryptedPublicUpload(handle);
|
|
99
|
+
return handle;
|
|
84
100
|
}
|
|
85
101
|
async function readViaEncryptedPublicUpload(handle) {
|
|
86
102
|
const descriptor = decodePublicUploadDescriptor(handle.id);
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
103
|
+
const startedAt = Date.now();
|
|
104
|
+
let response;
|
|
105
|
+
let attempts = 0;
|
|
106
|
+
for (let attempt = 0; attempt <= PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS.length; attempt++) {
|
|
107
|
+
attempts = attempt + 1;
|
|
108
|
+
try {
|
|
109
|
+
response = await fetch(descriptor.url);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (attempt === PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS.length) {
|
|
113
|
+
console.warn("[private-blob] public-upload read failed", {
|
|
114
|
+
attempts: attempt + 1,
|
|
115
|
+
elapsedMs: Date.now() - startedAt,
|
|
116
|
+
provider: handle.provider,
|
|
117
|
+
reason: "network",
|
|
118
|
+
});
|
|
119
|
+
throw new Error(`Private blob public-upload read failed: ${error instanceof Error ? error.message : "network error"}`, { cause: error });
|
|
120
|
+
}
|
|
121
|
+
await waitForPublicUploadRetry(PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS[attempt]);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (response.ok)
|
|
125
|
+
break;
|
|
126
|
+
if (!isRetryablePublicUploadStatus(response.status) ||
|
|
127
|
+
attempt === PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS.length) {
|
|
128
|
+
console.warn("[private-blob] public-upload read failed", {
|
|
129
|
+
attempts: attempt + 1,
|
|
130
|
+
elapsedMs: Date.now() - startedAt,
|
|
131
|
+
provider: handle.provider,
|
|
132
|
+
status: response.status,
|
|
133
|
+
});
|
|
134
|
+
throw new Error(`Private blob public-upload read failed (${response.status}): ${response.statusText}`);
|
|
135
|
+
}
|
|
136
|
+
await waitForPublicUploadRetry(PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS[attempt]);
|
|
137
|
+
}
|
|
138
|
+
if (!response?.ok) {
|
|
139
|
+
console.warn("[private-blob] public-upload read failed", {
|
|
140
|
+
attempts: PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS.length + 1,
|
|
141
|
+
elapsedMs: Date.now() - startedAt,
|
|
142
|
+
provider: handle.provider,
|
|
143
|
+
reason: "no-response",
|
|
144
|
+
});
|
|
145
|
+
throw new Error("Private blob public-upload read failed without a response");
|
|
146
|
+
}
|
|
147
|
+
if (attempts > 1) {
|
|
148
|
+
console.info("[private-blob] public-upload read recovered after retry", {
|
|
149
|
+
attempts,
|
|
150
|
+
elapsedMs: Date.now() - startedAt,
|
|
151
|
+
provider: handle.provider,
|
|
152
|
+
});
|
|
90
153
|
}
|
|
91
154
|
// The uploaded ciphertext is intentionally opaque; the descriptor carries
|
|
92
155
|
// auth tag + IV separately so the backing public URL is useless by itself.
|
|
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
|
|
|
48
48
|
}>;
|
|
49
49
|
/** DELETE /_agent-native/resources/:id — delete a resource */
|
|
50
50
|
export declare function handleDeleteResource(event: any): Promise<{
|
|
51
|
-
error: string;
|
|
52
51
|
ok?: undefined;
|
|
52
|
+
error: string;
|
|
53
53
|
} | {
|
|
54
54
|
error?: undefined;
|
|
55
55
|
ok: boolean;
|
|
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
26
26
|
expiresAt?: undefined;
|
|
27
27
|
ttlSeconds?: undefined;
|
|
28
28
|
} | {
|
|
29
|
-
error?: undefined;
|
|
30
29
|
token: string;
|
|
31
30
|
expiresAt: string;
|
|
32
31
|
ttlSeconds: number;
|
|
32
|
+
error?: undefined;
|
|
33
33
|
}>>;
|
|
@@ -168,6 +168,11 @@ the useful information remains dense and scannable.
|
|
|
168
168
|
- Do not build custom dropdowns, menus, popovers, modals, or confirmations with manual absolute positioning and click-outside effects.
|
|
169
169
|
- Never use browser dialogs (`window.alert`, `window.confirm`, `window.prompt`). Use `AlertDialog`, `Dialog`, or app-specific confirmation UI.
|
|
170
170
|
- Use Tabler icons for all first-party UI icons. Do not add Lucide, Heroicons, inline SVG icon sets, or emoji icons.
|
|
171
|
+
- Never guess a Tabler icon name. Names like `IconPartyPopper` or `IconConfettiCannon` feel
|
|
172
|
+
plausible but don't exist and crash Vite with a "Named export not found" error. Before importing
|
|
173
|
+
an icon you haven't used elsewhere in this app, confirm it exists by grepping
|
|
174
|
+
`node_modules/@tabler/icons-react/dist/tabler-icons-react.d.ts` (or the package's icon list)
|
|
175
|
+
for the exact name, and pick the closest real match if your first guess isn't there.
|
|
171
176
|
- Keep inline help/info glyphs next to labels at `size-3` (12px) or smaller than the adjacent text. Preserve a larger hit area on the trigger, not the glyph. Use `guard:allow-large-help-icon` only for deliberate heading documentation or menu action exceptions.
|
|
172
177
|
- Use `useActionQuery` and `useActionMutation` from `@agent-native/core/client` for action-backed UI. Standard CRUD should go through actions, not custom `/api/` routes.
|
|
173
178
|
- Keep UI optimistic where possible: update cache and navigation immediately, then reconcile or roll back on mutation result.
|
|
@@ -168,6 +168,11 @@ the useful information remains dense and scannable.
|
|
|
168
168
|
- Do not build custom dropdowns, menus, popovers, modals, or confirmations with manual absolute positioning and click-outside effects.
|
|
169
169
|
- Never use browser dialogs (`window.alert`, `window.confirm`, `window.prompt`). Use `AlertDialog`, `Dialog`, or app-specific confirmation UI.
|
|
170
170
|
- Use Tabler icons for all first-party UI icons. Do not add Lucide, Heroicons, inline SVG icon sets, or emoji icons.
|
|
171
|
+
- Never guess a Tabler icon name. Names like `IconPartyPopper` or `IconConfettiCannon` feel
|
|
172
|
+
plausible but don't exist and crash Vite with a "Named export not found" error. Before importing
|
|
173
|
+
an icon you haven't used elsewhere in this app, confirm it exists by grepping
|
|
174
|
+
`node_modules/@tabler/icons-react/dist/tabler-icons-react.d.ts` (or the package's icon list)
|
|
175
|
+
for the exact name, and pick the closest real match if your first guess isn't there.
|
|
171
176
|
- Keep inline help/info glyphs next to labels at `size-3` (12px) or smaller than the adjacent text. Preserve a larger hit area on the trigger, not the glyph. Use `guard:allow-large-help-icon` only for deliberate heading documentation or menu action exceptions.
|
|
172
177
|
- Use `useActionQuery` and `useActionMutation` from `@agent-native/core/client` for action-backed UI. Standard CRUD should go through actions, not custom `/api/` routes.
|
|
173
178
|
- Keep UI optimistic where possible: update cache and navigation immediately, then reconcile or roll back on mutation result.
|
|
@@ -2,6 +2,50 @@ overrides:
|
|
|
2
2
|
"@assistant-ui/store": ">=0.2.9 <0.2.14"
|
|
3
3
|
"@assistant-ui/tap": "^0.5.14"
|
|
4
4
|
nf3: "0.3.17"
|
|
5
|
+
# Tiptap 3.28 packages use caret ranges for their internal dependencies.
|
|
6
|
+
# Keep a fresh scaffold on one tested family so extension-list does not
|
|
7
|
+
# resolve to 3.30 alongside core 3.28 during a generated build.
|
|
8
|
+
"@tiptap/core": "3.28.0"
|
|
9
|
+
"@tiptap/extension-blockquote": "3.28.0"
|
|
10
|
+
"@tiptap/extension-bold": "3.28.0"
|
|
11
|
+
"@tiptap/extension-bubble-menu": "3.28.0"
|
|
12
|
+
"@tiptap/extension-bullet-list": "3.28.0"
|
|
13
|
+
"@tiptap/extension-code": "3.28.0"
|
|
14
|
+
"@tiptap/extension-code-block": "3.28.0"
|
|
15
|
+
"@tiptap/extension-code-block-lowlight": "3.28.0"
|
|
16
|
+
"@tiptap/extension-collaboration": "3.28.0"
|
|
17
|
+
"@tiptap/extension-collaboration-caret": "3.28.0"
|
|
18
|
+
"@tiptap/extension-color": "3.28.0"
|
|
19
|
+
"@tiptap/extension-document": "3.28.0"
|
|
20
|
+
"@tiptap/extension-dropcursor": "3.28.0"
|
|
21
|
+
"@tiptap/extension-floating-menu": "3.28.0"
|
|
22
|
+
"@tiptap/extension-gapcursor": "3.28.0"
|
|
23
|
+
"@tiptap/extension-hard-break": "3.28.0"
|
|
24
|
+
"@tiptap/extension-heading": "3.28.0"
|
|
25
|
+
"@tiptap/extension-horizontal-rule": "3.28.0"
|
|
26
|
+
"@tiptap/extension-image": "3.28.0"
|
|
27
|
+
"@tiptap/extension-italic": "3.28.0"
|
|
28
|
+
"@tiptap/extension-link": "3.28.0"
|
|
29
|
+
"@tiptap/extension-list": "3.28.0"
|
|
30
|
+
"@tiptap/extension-list-item": "3.28.0"
|
|
31
|
+
"@tiptap/extension-list-keymap": "3.28.0"
|
|
32
|
+
"@tiptap/extension-ordered-list": "3.28.0"
|
|
33
|
+
"@tiptap/extension-paragraph": "3.28.0"
|
|
34
|
+
"@tiptap/extension-placeholder": "3.28.0"
|
|
35
|
+
"@tiptap/extension-strike": "3.28.0"
|
|
36
|
+
"@tiptap/extension-table": "3.28.0"
|
|
37
|
+
"@tiptap/extension-table-cell": "3.28.0"
|
|
38
|
+
"@tiptap/extension-table-header": "3.28.0"
|
|
39
|
+
"@tiptap/extension-table-row": "3.28.0"
|
|
40
|
+
"@tiptap/extension-task-item": "3.28.0"
|
|
41
|
+
"@tiptap/extension-task-list": "3.28.0"
|
|
42
|
+
"@tiptap/extension-text": "3.28.0"
|
|
43
|
+
"@tiptap/extension-text-style": "3.28.0"
|
|
44
|
+
"@tiptap/extension-underline": "3.28.0"
|
|
45
|
+
"@tiptap/extensions": "3.28.0"
|
|
46
|
+
"@tiptap/pm": "3.28.0"
|
|
47
|
+
"@tiptap/react": "3.28.0"
|
|
48
|
+
"@tiptap/starter-kit": "3.28.0"
|
|
5
49
|
|
|
6
50
|
allowBuilds:
|
|
7
51
|
better-sqlite3: true
|
|
@@ -168,6 +168,11 @@ the useful information remains dense and scannable.
|
|
|
168
168
|
- Do not build custom dropdowns, menus, popovers, modals, or confirmations with manual absolute positioning and click-outside effects.
|
|
169
169
|
- Never use browser dialogs (`window.alert`, `window.confirm`, `window.prompt`). Use `AlertDialog`, `Dialog`, or app-specific confirmation UI.
|
|
170
170
|
- Use Tabler icons for all first-party UI icons. Do not add Lucide, Heroicons, inline SVG icon sets, or emoji icons.
|
|
171
|
+
- Never guess a Tabler icon name. Names like `IconPartyPopper` or `IconConfettiCannon` feel
|
|
172
|
+
plausible but don't exist and crash Vite with a "Named export not found" error. Before importing
|
|
173
|
+
an icon you haven't used elsewhere in this app, confirm it exists by grepping
|
|
174
|
+
`node_modules/@tabler/icons-react/dist/tabler-icons-react.d.ts` (or the package's icon list)
|
|
175
|
+
for the exact name, and pick the closest real match if your first guess isn't there.
|
|
171
176
|
- Keep inline help/info glyphs next to labels at `size-3` (12px) or smaller than the adjacent text. Preserve a larger hit area on the trigger, not the glyph. Use `guard:allow-large-help-icon` only for deliberate heading documentation or menu action exceptions.
|
|
172
177
|
- Use `useActionQuery` and `useActionMutation` from `@agent-native/core/client` for action-backed UI. Standard CRUD should go through actions, not custom `/api/` routes.
|
|
173
178
|
- Keep UI optimistic where possible: update cache and navigation immediately, then reconcile or roll back on mutation result.
|