@agent-native/core 0.77.15 → 0.77.17
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/core/CHANGELOG.md +19 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +46 -33
- package/corpus/core/src/client/AgentPanel.tsx +4 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +4 -0
- package/corpus/core/src/client/use-db-sync.ts +19 -2
- package/corpus/core/src/server/auth-marketing.ts +15 -0
- package/corpus/core/src/server/onboarding-html.ts +47 -1
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +76 -0
- package/corpus/templates/analytics/actions/update-dashboard.ts +1 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +13 -2
- package/corpus/templates/analytics/app/i18n-data.ts +10 -0
- package/corpus/templates/analytics/app/lib/sql-query.ts +97 -21
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +22 -1
- package/corpus/templates/analytics/app/root.tsx +15 -6
- package/corpus/templates/analytics/changelog/2026-06-25-dashboard-charts-no-longer-refresh-in-the-background-during-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-25-first-party-dashboards-use-indexed-event-dates-for-faster-da.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-25-retention-and-active-user-dashboard-panels-now-count-account.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +58 -58
- package/corpus/templates/analytics/server/db/schema.ts +2 -0
- package/corpus/templates/analytics/server/handlers/sql-query.ts +1 -1
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +1 -1
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +16 -6
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +88 -56
- package/corpus/templates/analytics/server/plugins/db.ts +73 -0
- package/corpus/templates/clips/AGENTS.md +5 -0
- package/corpus/templates/clips/changelog/2026-06-25-github-issue-and-pull-request-pages-can-now-preview-playable.md +6 -0
- package/corpus/templates/clips/chrome-extension/PERMISSIONS.md +8 -2
- package/corpus/templates/clips/chrome-extension/public/manifest.json +14 -2
- package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +233 -0
- package/corpus/templates/clips/chrome-extension/src/github-preview.html +12 -0
- package/corpus/templates/clips/chrome-extension/src/github-preview.ts +414 -0
- package/corpus/templates/clips/chrome-extension/vite.config.ts +5 -0
- package/corpus/templates/mail/actions/archive-email.ts +13 -3
- package/corpus/templates/mail/app/hooks/use-emails.ts +5 -2
- package/corpus/templates/mail/changelog/2026-06-25-archive-failures-now-explain-when-gmail-needs-reconnecting-p.md +6 -0
- package/corpus/templates/mail/shared/archive-errors.ts +137 -0
- package/corpus/templates/plan/changelog/2026-06-25-hosted-signup-now-shows-how-to-switch-visual-plan-to-local-files.md +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +17 -12
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +3 -1
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +9 -0
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +8 -1
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/server/agent-engine-api-key-route.d.ts +2 -2
- package/dist/server/auth-marketing.d.ts +4 -0
- package/dist/server/auth-marketing.d.ts.map +1 -1
- package/dist/server/auth-marketing.js +9 -0
- package/dist/server/auth-marketing.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +46 -1
- package/dist/server/onboarding-html.js.map +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.77.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6c65e7f: Show a Plan signup note for switching `/visual-plan` to local-files mode.
|
|
8
|
+
- 6c65e7f: Allow apps to filter the broad action-event query invalidation from `useDbSync`.
|
|
9
|
+
- 6c65e7f: Keep standalone scaffold installs stable when fresh Sentry transitive packages are published.
|
|
10
|
+
|
|
11
|
+
## 0.77.16
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 02af11c: Extend the background-worker pre-send instrumentation: also time out the required
|
|
16
|
+
suspect branches (system prompt / `extraContext`, enriched message) and record
|
|
17
|
+
which branch hit its timeout/error fallback into the run's setup diagnostic
|
|
18
|
+
(`to=...` in `setupDetail`). The hanging op is then identifiable via
|
|
19
|
+
`/runs/active` even when the Netlify function-log drain is unreadable, and the
|
|
20
|
+
timeout lets the worker claim past a stuck required read.
|
|
21
|
+
|
|
3
22
|
## 0.77.15
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.77.
|
|
3
|
+
"version": "0.77.17",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -3963,10 +3963,15 @@ export function createProductionAgentHandler(
|
|
|
3963
3963
|
);
|
|
3964
3964
|
} catch {}
|
|
3965
3965
|
};
|
|
3966
|
-
// DIAGNOSTIC
|
|
3967
|
-
//
|
|
3968
|
-
//
|
|
3969
|
-
//
|
|
3966
|
+
// DIAGNOSTIC: pre-send branches that hit their timeout/error fallback,
|
|
3967
|
+
// recorded in memory so the FINAL run diag (setupDetail) can name them even
|
|
3968
|
+
// when the Netlify function-log drain is unreadable. Readable via
|
|
3969
|
+
// /runs/active once the worker claims past the stuck branch.
|
|
3970
|
+
const bgTimedOut: string[] = [];
|
|
3971
|
+
// DIAGNOSTIC + DEFENSIVE: race a pre-send read against a short timeout and
|
|
3972
|
+
// fall back to a safe default, logging start/done/error/timeout. A single
|
|
3973
|
+
// stuck read can no longer block the worker from reaching claim, and the
|
|
3974
|
+
// recorded `bgTimedOut` set names the culprit in the run diag.
|
|
3970
3975
|
const withBgFallback = <T>(
|
|
3971
3976
|
label: string,
|
|
3972
3977
|
ms: number,
|
|
@@ -3980,6 +3985,7 @@ export function createProductionAgentHandler(
|
|
|
3980
3985
|
if (done) return;
|
|
3981
3986
|
done = true;
|
|
3982
3987
|
bgLog(`${label}:TIMEOUT@${ms}ms`);
|
|
3988
|
+
bgTimedOut.push(`${label}:timeout`);
|
|
3983
3989
|
resolve(fallback);
|
|
3984
3990
|
}, ms);
|
|
3985
3991
|
fn().then(
|
|
@@ -3997,6 +4003,7 @@ export function createProductionAgentHandler(
|
|
|
3997
4003
|
bgLog(
|
|
3998
4004
|
`${label}:error ${(e as { message?: string })?.message ?? e}`,
|
|
3999
4005
|
);
|
|
4006
|
+
bgTimedOut.push(`${label}:error`);
|
|
4000
4007
|
resolve(fallback);
|
|
4001
4008
|
},
|
|
4002
4009
|
);
|
|
@@ -4270,13 +4277,12 @@ export function createProductionAgentHandler(
|
|
|
4270
4277
|
// that starts but never finishes; the OPTIONAL context reads additionally
|
|
4271
4278
|
// race a short timeout + "" fallback (withBgFallback) so one stuck read
|
|
4272
4279
|
// cannot block the worker from reaching claim.
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
});
|
|
4280
|
+
const enrichedMessagePromise = withBgFallback(
|
|
4281
|
+
"enrich",
|
|
4282
|
+
5000,
|
|
4283
|
+
requestMessage,
|
|
4284
|
+
() => Promise.resolve(enrichMessage(requestMessage, references)),
|
|
4285
|
+
);
|
|
4280
4286
|
bgLog("loop:start");
|
|
4281
4287
|
const loopSettingsPromise = readAgentLoopSettings({
|
|
4282
4288
|
userEmail: ownerEmail ?? getRequestUserEmail() ?? null,
|
|
@@ -4289,27 +4295,32 @@ export function createProductionAgentHandler(
|
|
|
4289
4295
|
});
|
|
4290
4296
|
|
|
4291
4297
|
let systemPromptError: any = null;
|
|
4292
|
-
const systemPromptPromise = (
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4298
|
+
const systemPromptPromise = withBgFallback(
|
|
4299
|
+
"sysprompt",
|
|
4300
|
+
5000,
|
|
4301
|
+
"",
|
|
4302
|
+
async (): Promise<string> => {
|
|
4303
|
+
const sysPromptStart = Date.now();
|
|
4304
|
+
bgLog("sysprompt:start");
|
|
4305
|
+
try {
|
|
4306
|
+
const sp =
|
|
4307
|
+
typeof options.systemPrompt === "function"
|
|
4308
|
+
? await options.systemPrompt(event)
|
|
4309
|
+
: options.systemPrompt;
|
|
4310
|
+
bgLog("sysprompt:done");
|
|
4311
|
+
return sp;
|
|
4312
|
+
} catch (error) {
|
|
4313
|
+
systemPromptError = error;
|
|
4314
|
+
bgLog(
|
|
4315
|
+
"sysprompt:error " +
|
|
4316
|
+
((error as { message?: string })?.message ?? error),
|
|
4317
|
+
);
|
|
4318
|
+
return "";
|
|
4319
|
+
} finally {
|
|
4320
|
+
setupMarks.sysPromptMs = Date.now() - sysPromptStart;
|
|
4321
|
+
}
|
|
4322
|
+
},
|
|
4323
|
+
);
|
|
4313
4324
|
|
|
4314
4325
|
const screenContextPromise = withBgFallback(
|
|
4315
4326
|
"screen",
|
|
@@ -5164,7 +5175,9 @@ export function createProductionAgentHandler(
|
|
|
5164
5175
|
const setupDetail =
|
|
5165
5176
|
Object.entries(setupMarks)
|
|
5166
5177
|
.map(([k, v]) => `${k}=${v}`)
|
|
5167
|
-
.join(" ") +
|
|
5178
|
+
.join(" ") +
|
|
5179
|
+
` total=${Date.now() - setupT0}` +
|
|
5180
|
+
` to=${bgTimedOut.join(",") || "none"}`;
|
|
5168
5181
|
|
|
5169
5182
|
bgLog("startRun:invoked");
|
|
5170
5183
|
startRun(
|
|
@@ -687,6 +687,7 @@ function AgentPanelInner({
|
|
|
687
687
|
);
|
|
688
688
|
const closeTabHint = isMac ? "\u2303W" : "Alt+W";
|
|
689
689
|
const closeAllTabsHint = isMac ? "\u2303\u2325W" : "Ctrl+Alt+W";
|
|
690
|
+
const toggleSidebarHint = isMac ? "\u2318\\" : "Ctrl+\\";
|
|
690
691
|
|
|
691
692
|
const [execMode, setExecMode] = useState<ExecMode>(() => {
|
|
692
693
|
try {
|
|
@@ -1125,6 +1126,9 @@ function AgentPanelInner({
|
|
|
1125
1126
|
className="shrink-0"
|
|
1126
1127
|
/>
|
|
1127
1128
|
{t("agentPanel.collapseSidebar")}
|
|
1129
|
+
<DropdownMenuShortcut>
|
|
1130
|
+
{toggleSidebarHint}
|
|
1131
|
+
</DropdownMenuShortcut>
|
|
1128
1132
|
</DropdownMenuItem>
|
|
1129
1133
|
<DropdownMenuSeparator />
|
|
1130
1134
|
</>
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
IconCode,
|
|
16
16
|
IconContract,
|
|
17
17
|
IconFileText,
|
|
18
|
+
IconLayoutBoard,
|
|
18
19
|
IconLoader2,
|
|
19
20
|
IconLogout,
|
|
20
21
|
IconMail,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
IconPhoto,
|
|
26
27
|
IconPlus,
|
|
27
28
|
IconPresentation,
|
|
29
|
+
IconRoute,
|
|
28
30
|
IconScreenShare,
|
|
29
31
|
IconSelector,
|
|
30
32
|
IconSettings,
|
|
@@ -101,6 +103,7 @@ const APP_ICON_MAP: Record<string, typeof IconApps> = {
|
|
|
101
103
|
Mail: IconMail,
|
|
102
104
|
CalendarDays: IconCalendar,
|
|
103
105
|
FileText: IconFileText,
|
|
106
|
+
LayoutBoard: IconLayoutBoard,
|
|
104
107
|
BarChart2: IconChartBar,
|
|
105
108
|
GalleryHorizontal: IconPresentation,
|
|
106
109
|
Video: IconVideo,
|
|
@@ -110,6 +113,7 @@ const APP_ICON_MAP: Record<string, typeof IconApps> = {
|
|
|
110
113
|
Code: IconCode,
|
|
111
114
|
Contract: IconContract,
|
|
112
115
|
MessageCircle: IconMessageCircle,
|
|
116
|
+
Route: IconRoute,
|
|
113
117
|
ScreenShare: IconScreenShare,
|
|
114
118
|
Brush: IconBrush,
|
|
115
119
|
Brain: IconBrain,
|
|
@@ -8,8 +8,15 @@ import {
|
|
|
8
8
|
} from "./embed-auth.js";
|
|
9
9
|
import { bumpChangeVersion } from "./use-change-version.js";
|
|
10
10
|
|
|
11
|
+
interface Query {
|
|
12
|
+
queryKey: readonly unknown[];
|
|
13
|
+
}
|
|
14
|
+
|
|
11
15
|
interface QueryClient {
|
|
12
|
-
invalidateQueries(opts?: {
|
|
16
|
+
invalidateQueries(opts?: {
|
|
17
|
+
queryKey?: string[];
|
|
18
|
+
predicate?: (query: Query) => boolean;
|
|
19
|
+
}): void;
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
const POLL_ABORT_MIN_MS = 10_000;
|
|
@@ -475,6 +482,10 @@ export function _resetSyncTransportRegistryForTests(): void {
|
|
|
475
482
|
* @param options.ignoreSource - Skip events whose `requestSource` matches this
|
|
476
483
|
* value. Use a per-tab ID so the UI ignores its own writes while still
|
|
477
484
|
* picking up changes from other tabs, agents, and scripts.
|
|
485
|
+
* @param options.actionInvalidatePredicate - Optional filter for the broad
|
|
486
|
+
* compatibility invalidate triggered by `action` events. Use this to keep
|
|
487
|
+
* expensive active queries on explicit-refresh semantics while still letting
|
|
488
|
+
* normal source-versioned queries react through `useChangeVersion`.
|
|
478
489
|
*/
|
|
479
490
|
export function useDbSync(
|
|
480
491
|
options: {
|
|
@@ -489,6 +500,7 @@ export function useDbSync(
|
|
|
489
500
|
fallbackInterval?: number;
|
|
490
501
|
pauseWhenHidden?: boolean;
|
|
491
502
|
ignoreSource?: string;
|
|
503
|
+
actionInvalidatePredicate?: (query: Query) => boolean;
|
|
492
504
|
} = {},
|
|
493
505
|
): void {
|
|
494
506
|
const {
|
|
@@ -508,6 +520,10 @@ export function useDbSync(
|
|
|
508
520
|
|
|
509
521
|
const ignoreSourceRef = useRef(options.ignoreSource);
|
|
510
522
|
ignoreSourceRef.current = options.ignoreSource;
|
|
523
|
+
const actionInvalidatePredicateRef = useRef(
|
|
524
|
+
options.actionInvalidatePredicate,
|
|
525
|
+
);
|
|
526
|
+
actionInvalidatePredicateRef.current = options.actionInvalidatePredicate;
|
|
511
527
|
|
|
512
528
|
useEffect(() => {
|
|
513
529
|
const id = Symbol("useDbSync");
|
|
@@ -550,7 +566,8 @@ export function useDbSync(
|
|
|
550
566
|
// signal, so refresh active queries broadly as a compatibility
|
|
551
567
|
// safety net. Other event sources stay targeted to avoid request
|
|
552
568
|
// storms from noisy domain-specific writes.
|
|
553
|
-
|
|
569
|
+
const predicate = actionInvalidatePredicateRef.current;
|
|
570
|
+
queryClient.invalidateQueries(predicate ? { predicate } : undefined);
|
|
554
571
|
}
|
|
555
572
|
|
|
556
573
|
// Framework-level invalidate: a small, fixed list of query-key
|
|
@@ -4,8 +4,15 @@ export interface AuthMarketingContent {
|
|
|
4
4
|
description?: string;
|
|
5
5
|
features?: string[];
|
|
6
6
|
runLocalCommand?: string;
|
|
7
|
+
signupLocalModeNote?: {
|
|
8
|
+
text: string;
|
|
9
|
+
command: string;
|
|
10
|
+
};
|
|
7
11
|
}
|
|
8
12
|
|
|
13
|
+
const PLAN_LOCAL_FILES_COMMAND =
|
|
14
|
+
"npx @agent-native/core@latest skills add visual-plan --mode local-files --scope user";
|
|
15
|
+
|
|
9
16
|
export interface ResolveBuiltInAuthMarketingOptions {
|
|
10
17
|
requestHost?: string;
|
|
11
18
|
requestPath?: string;
|
|
@@ -71,6 +78,11 @@ export const BUILT_IN_AUTH_MARKETING: Record<string, AuthMarketingContent> = {
|
|
|
71
78
|
"Review PR recaps with diagrams, file maps, and annotated code",
|
|
72
79
|
"Share links for async comments and product review",
|
|
73
80
|
],
|
|
81
|
+
runLocalCommand: PLAN_LOCAL_FILES_COMMAND,
|
|
82
|
+
signupLocalModeNote: {
|
|
83
|
+
text: "Prefer no account or self-hosting? Switch /visual-plan to local files only:",
|
|
84
|
+
command: PLAN_LOCAL_FILES_COMMAND,
|
|
85
|
+
},
|
|
74
86
|
},
|
|
75
87
|
design: {
|
|
76
88
|
appName: "Agent-Native Design",
|
|
@@ -169,6 +181,9 @@ function cloneMarketing(marketing: AuthMarketingContent): AuthMarketingContent {
|
|
|
169
181
|
return {
|
|
170
182
|
...marketing,
|
|
171
183
|
features: marketing.features ? [...marketing.features] : undefined,
|
|
184
|
+
signupLocalModeNote: marketing.signupLocalModeNote
|
|
185
|
+
? { ...marketing.signupLocalModeNote }
|
|
186
|
+
: undefined,
|
|
172
187
|
};
|
|
173
188
|
}
|
|
174
189
|
|
|
@@ -1254,6 +1254,14 @@ export function getOnboardingHtml(opts: OnboardingHtmlOptions = {}): string {
|
|
|
1254
1254
|
}
|
|
1255
1255
|
: undefined;
|
|
1256
1256
|
const runLocalCommand = marketing?.runLocalCommand?.trim();
|
|
1257
|
+
const signupLocalModeNote =
|
|
1258
|
+
isAgentNativeHostedHost(opts.requestHost) &&
|
|
1259
|
+
marketing?.signupLocalModeNote?.command.trim()
|
|
1260
|
+
? {
|
|
1261
|
+
text: marketing.signupLocalModeNote.text.trim(),
|
|
1262
|
+
command: marketing.signupLocalModeNote.command.trim(),
|
|
1263
|
+
}
|
|
1264
|
+
: undefined;
|
|
1257
1265
|
const brandMarkSrc = withAppBasePath("/agent-native-icon-dark.svg");
|
|
1258
1266
|
const socialImageUrl = withAgentNativeSocialImageCacheBuster(
|
|
1259
1267
|
opts.requestOrigin
|
|
@@ -1336,6 +1344,13 @@ ${localeMenuItemsHtml}
|
|
|
1336
1344
|
const signupLegalNoteHtml = signupLegalNotice
|
|
1337
1345
|
? ` <p class="legal-note">${localizedValue(signupLegalNotice.prefix, "legalPrefix")} <a href="${esc(signupLegalNotice.termsUrl)}" target="_blank" rel="noreferrer"${localizedAnchorLabel(signupLegalNotice.termsLabel, "legalTerms")}</a> ${localizedValue(signupLegalNotice.connector, "legalConnector")} <a href="${esc(signupLegalNotice.privacyUrl)}" target="_blank" rel="noreferrer"${localizedAnchorLabel(signupLegalNotice.privacyLabel, "legalPrivacy")}</a>${localizedValue(signupLegalNotice.suffix, "legalSuffix")}</p>`
|
|
1338
1346
|
: "";
|
|
1347
|
+
const signupLocalModeNoteHtml = signupLocalModeNote
|
|
1348
|
+
? ` <div class="signup-local-mode-note" id="signup-local-mode-note" data-command="${esc(signupLocalModeNote.command)}">
|
|
1349
|
+
<p>${esc(signupLocalModeNote.text)}</p>
|
|
1350
|
+
<code>${esc(signupLocalModeNote.command)}</code>
|
|
1351
|
+
<button type="button" class="copy-run-local" id="copy-signup-local-mode" onclick="__anCopySignupLocalModeCommand()"${i18nAttr("copyCommand")}>${esc(t("copyCommand"))}</button>
|
|
1352
|
+
</div>`
|
|
1353
|
+
: "";
|
|
1339
1354
|
const googleSignInNotice = opts.googleSignInNotice;
|
|
1340
1355
|
const googleNoticeBodyParts = googleSignInNotice
|
|
1341
1356
|
? (Array.isArray(googleSignInNotice.body)
|
|
@@ -2025,6 +2040,33 @@ ${
|
|
|
2025
2040
|
text-underline-offset: 2px;
|
|
2026
2041
|
}
|
|
2027
2042
|
.legal-note a:hover { color: #aaa; }
|
|
2043
|
+
.signup-local-mode-note {
|
|
2044
|
+
margin-top: 0.75rem;
|
|
2045
|
+
padding: 0.625rem;
|
|
2046
|
+
color: #777;
|
|
2047
|
+
background: rgba(255,255,255,0.025);
|
|
2048
|
+
border: 1px solid rgba(255,255,255,0.08);
|
|
2049
|
+
border-radius: 8px;
|
|
2050
|
+
font-size: 0.6875rem;
|
|
2051
|
+
line-height: 1.45;
|
|
2052
|
+
text-align: left;
|
|
2053
|
+
}
|
|
2054
|
+
.signup-local-mode-note p {
|
|
2055
|
+
margin: 0 0 0.5rem;
|
|
2056
|
+
}
|
|
2057
|
+
.signup-local-mode-note code {
|
|
2058
|
+
display: block;
|
|
2059
|
+
overflow-x: auto;
|
|
2060
|
+
padding-bottom: 0.125rem;
|
|
2061
|
+
color: #b8b8b8;
|
|
2062
|
+
font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
|
2063
|
+
font-size: 0.6875rem;
|
|
2064
|
+
line-height: 1.5;
|
|
2065
|
+
white-space: nowrap;
|
|
2066
|
+
}
|
|
2067
|
+
.signup-local-mode-note .copy-run-local {
|
|
2068
|
+
margin-top: 0.5rem;
|
|
2069
|
+
}
|
|
2028
2070
|
.msg { margin-top: 0.75rem; font-size: 0.8125rem; display: none; }
|
|
2029
2071
|
.msg.error { color: #f87171; }
|
|
2030
2072
|
.msg.success { color: #33C4FF; }
|
|
@@ -2376,6 +2418,7 @@ ${
|
|
|
2376
2418
|
<input id="s-pass2" type="password" autocomplete="new-password" placeholder="${esc(t("confirmPasswordPlaceholder"))}"${i18nPlaceholderAttr("confirmPasswordPlaceholder")} required minlength="8" />
|
|
2377
2419
|
<button type="submit"${i18nAttr("createAccount")}>${esc(t("createAccount"))}</button>
|
|
2378
2420
|
${signupLegalNoteHtml}
|
|
2421
|
+
${signupLocalModeNoteHtml}
|
|
2379
2422
|
<p class="msg" id="s-msg"></p>
|
|
2380
2423
|
</form>
|
|
2381
2424
|
|
|
@@ -3705,7 +3748,7 @@ ${
|
|
|
3705
3748
|
}
|
|
3706
3749
|
${starfieldScript}
|
|
3707
3750
|
${
|
|
3708
|
-
runLocalCommand
|
|
3751
|
+
runLocalCommand || signupLocalModeNote
|
|
3709
3752
|
? `
|
|
3710
3753
|
function __anSetRunLocalCommandOpen(open) {
|
|
3711
3754
|
var panel = document.getElementById('run-local-panel');
|
|
@@ -3740,6 +3783,9 @@ ${
|
|
|
3740
3783
|
function __anCopyRunLocalCommand() {
|
|
3741
3784
|
__anCopyCommandFromPanel('run-local-panel', 'copy-run-local');
|
|
3742
3785
|
}
|
|
3786
|
+
function __anCopySignupLocalModeCommand() {
|
|
3787
|
+
__anCopyCommandFromPanel('signup-local-mode-note', 'copy-signup-local-mode');
|
|
3788
|
+
}
|
|
3743
3789
|
function __anCopyGoogleNoticeRunLocalCommand() {
|
|
3744
3790
|
__anCopyCommandFromPanel('google-preflight-run-local-panel', 'copy-google-preflight-run-local');
|
|
3745
3791
|
}`
|
|
@@ -25,6 +25,12 @@ Legacy settings keys such as `u:<email>:dashboard-*`, `u:<email>:sql-dashboard-*
|
|
|
25
25
|
|
|
26
26
|
Use `update-dashboard` for dashboard edits. It resolves the current user/org context, validates the config, applies JSON-pointer operations when provided, writes the SQL-backed record, and preserves sharing semantics.
|
|
27
27
|
|
|
28
|
+
Never use `db-patch`, raw SQL, or settings-key edits to create or modify a
|
|
29
|
+
dashboard config. Those bypass the dashboard action's access checks, SQL
|
|
30
|
+
validation, collab sync, and proof-of-done return. If an `update-dashboard`
|
|
31
|
+
call fails because the argument shape was wrong, fix the `update-dashboard`
|
|
32
|
+
arguments and retry once — do not switch tools.
|
|
33
|
+
|
|
28
34
|
## Valid Panel Sources
|
|
29
35
|
|
|
30
36
|
`panel.source` is a backend selector, not a table name. It must be one of:
|
|
@@ -174,6 +180,73 @@ pnpm action update-dashboard --dashboardId weekly-metrics --config '<full json>'
|
|
|
174
180
|
|
|
175
181
|
After a mutation, navigate to the dashboard if the user is elsewhere. The app syncs through the framework's polling/query invalidation path.
|
|
176
182
|
|
|
183
|
+
### Existing Dashboard Edits
|
|
184
|
+
|
|
185
|
+
When the user asks to change existing panels:
|
|
186
|
+
|
|
187
|
+
1. Read the current dashboard config through the dashboard/action surface.
|
|
188
|
+
2. Find panel indexes by `panel.id` from the current config you just read. Do
|
|
189
|
+
not rely on seed-file order, stale memory, or screenshots.
|
|
190
|
+
3. Build one `ops` array that includes every change.
|
|
191
|
+
4. Call `update-dashboard` once.
|
|
192
|
+
5. Verify the returned `panelCount`, `appliedOps`, and `summary`. If possible,
|
|
193
|
+
read the affected panels back and confirm the exact fields changed.
|
|
194
|
+
|
|
195
|
+
For native production tools, pass `ops` as a native array:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"dashboardId": "weekly-metrics",
|
|
200
|
+
"ops": [
|
|
201
|
+
{
|
|
202
|
+
"op": "replace",
|
|
203
|
+
"path": "/panels/3/sql",
|
|
204
|
+
"value": "SELECT COUNT(*) AS value FROM analytics_events"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"op": "replace",
|
|
208
|
+
"path": "/panels/3/config/description",
|
|
209
|
+
"value": "Updated definition."
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The quoted JSON examples in this skill are for shell commands only. In native
|
|
216
|
+
tool calls, do not pass `ops` as a string. If the tool complains about the
|
|
217
|
+
shape, retry `update-dashboard` with a native array and continue from the same
|
|
218
|
+
dashboard config.
|
|
219
|
+
|
|
220
|
+
For SQL-only panel edits, replacing `/panels/<index>/sql` is enough. If the
|
|
221
|
+
metric semantics changed, also replace `/panels/<index>/config/description` so
|
|
222
|
+
the visible dashboard explains the new definition. If the title, source, chart
|
|
223
|
+
type, width, or config shape changes together, replace the whole panel object at
|
|
224
|
+
`/panels/<index>` in the same `ops` array.
|
|
225
|
+
|
|
226
|
+
### First-Party User Metrics
|
|
227
|
+
|
|
228
|
+
For first-party `/track` events, be precise about identity:
|
|
229
|
+
|
|
230
|
+
| Metric intent | Identity expression |
|
|
231
|
+
| -------------------------------------------- | -------------------------------------------------------- |
|
|
232
|
+
| Account users, DAU, WAU, retention, cohorts | `NULLIF(user_id, '')` plus `NULLIF(user_id, '') IS NOT NULL`, but only on events that actually represent the activity being measured |
|
|
233
|
+
| Signed-in visitor activity | `event_name = 'session status' AND signed_in = 'true'` keyed by `COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))`, labeled as signed-in visitors rather than account users |
|
|
234
|
+
| Public traffic, visitors, clip/share viewers | `COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))` |
|
|
235
|
+
|
|
236
|
+
Do not call anonymous visitors "users" in dashboard labels or descriptions.
|
|
237
|
+
When a user asks for DAU, WAU, retention, repeat users, or account cohorts,
|
|
238
|
+
exclude logged-out traffic unless they explicitly ask for visitor metrics. If
|
|
239
|
+
the active/session events do not include account identity, do not substitute
|
|
240
|
+
signup or identify events and call that DAU/WAU. Either update instrumentation to
|
|
241
|
+
send account identity on active events, or label the dashboard metric as
|
|
242
|
+
signed-in visitor activity.
|
|
243
|
+
|
|
244
|
+
For template/app activity metrics, exclude `docs` from DAU, WAU, retention, and
|
|
245
|
+
repeat-user panels. A docs event may carry `signed_in = true` from shared auth
|
|
246
|
+
state or tracker context, but docs traffic is not app usage and should not appear
|
|
247
|
+
as an app/template series. Use a minimum cohort-size threshold for retention
|
|
248
|
+
rates so one or two identities cannot create misleading 100% or 0% spikes.
|
|
249
|
+
|
|
177
250
|
## Building Large First-Party Dashboards (compose-dashboard)
|
|
178
251
|
|
|
179
252
|
For a **first-party analytics** dashboard, prefer `compose-dashboard` over hand-authoring a big `update-dashboard` config. You name the metrics; the SERVER expands each into a full, validated panel (SQL + chart config) from the shipped metric catalog and saves them in ONE atomic call. This avoids the failure mode where the agent must stream a giant multi-panel `update-dashboard` argument inside the ~40s budget — that big tool-call can't be resumed mid-stream and is all-or-nothing on validation, so the agent thrashes (repeated update-dashboard + tool-search, never landing).
|
|
@@ -202,6 +275,7 @@ Hosted agent runs have a **~40s budget**. Many sequential `update-dashboard` cal
|
|
|
202
275
|
- **Batch ALL changes into ONE `update-dashboard` call.** A single `update-dashboard` is atomic: it applies every op to an in-memory config, validates all panel SQL, then upserts once. Never loop the action.
|
|
203
276
|
- To add N panels, pass N ops in one call: `ops: [{op:"insert", path:"/panels/-", value:<panel>}, … ]` (`/panels/-` appends to the end).
|
|
204
277
|
- The `ops` format needs no discovery: each op is `{ op, path, from?, value? }`, `op ∈ set | replace | remove | insert | move | move-before`, and `path` is a JSON Pointer (e.g. `/panels/3`, `/panels/3/title`, `/name`).
|
|
278
|
+
- In native tool calls, `ops` is an array, not a JSON string. Shell commands quote JSON only because shells need strings.
|
|
205
279
|
- **To add a shipped template's panels, prefer `install-dashboard-template` with `mergePanels: true`** and the existing `dashboardId`. It appends only the template panels whose id is not already present (preserving existing panels and order) in one atomic save — you don't author each panel yourself.
|
|
206
280
|
- **Always verify the returned proof-of-done and report it.** `update-dashboard` returns `panelCount`, `appliedOps`, and a `summary` string; `install-dashboard-template --mergePanels` returns `addedPanelIds`, `skippedExistingIds`, and `panelCount`. Tell the user the resulting panel count instead of assuming success.
|
|
207
281
|
|
|
@@ -243,6 +317,8 @@ Writes require editor access; deletes require admin access. Owners always satisf
|
|
|
243
317
|
|
|
244
318
|
- Never fabricate data or create a dashboard from guessed schema. A panel's SQL must hit a real source; do not present figures you did not actually query.
|
|
245
319
|
- Never write dashboard configs into the settings table.
|
|
320
|
+
- Never use `db-patch` as a fallback for dashboard config edits. Use
|
|
321
|
+
`update-dashboard` and fix the action arguments.
|
|
246
322
|
- Never set `panel.source` to a table name or unsupported backend.
|
|
247
323
|
- Use `first-party` for `/track` data and `query-agent-native-analytics` for ad-hoc first-party event questions.
|
|
248
324
|
- Use `update-dashboard` for creates and edits.
|
|
@@ -416,6 +416,7 @@ export default defineAction({
|
|
|
416
416
|
"it resolves org vs. user scope correctly so the edit lands on the row the UI actually renders. " +
|
|
417
417
|
"BATCH ALL EDITS INTO A SINGLE CALL. Never call this action repeatedly in a loop: hosted agent runs have a ~40s budget, and many sequential update-dashboard calls time out mid-way and leave the dashboard in a partial state even though earlier calls looked like they succeeded. Put every change you want to make into one `ops` array (or one `config`). " +
|
|
418
418
|
"`ops` is an array of { op, path, from?, value? } applied in order in a single atomic save. " +
|
|
419
|
+
"When calling this as a native tool, pass `ops` as a real array, not a JSON string; the shell examples quote JSON only for CLI parsing. If a call fails because `ops` was stringified, retry this action with a native array instead of switching to db-patch or settings writes. " +
|
|
419
420
|
"`op` is one of: set | replace | remove | insert | move | move-before. " +
|
|
420
421
|
"`path` is a JSON Pointer into the config (e.g. `/panels/3` is the 4th panel, `/panels/3/title` is its title, `/name` is the dashboard name). The special index `-` means the end of an array: `/panels/-` appends. " +
|
|
421
422
|
"`value` is the panel or object to set/insert. `from` is the source JSON Pointer for move / move-before. " +
|
|
@@ -850,7 +850,11 @@ export function SqlChart({
|
|
|
850
850
|
const isSection = panel.chartType === "section";
|
|
851
851
|
const shouldQuery = !isSection && loadData;
|
|
852
852
|
const sql = serializePanelSql(resolvedSql ?? panel.sql);
|
|
853
|
-
const {
|
|
853
|
+
const {
|
|
854
|
+
data: result,
|
|
855
|
+
isLoading,
|
|
856
|
+
error: queryError,
|
|
857
|
+
} = useSqlQuery(
|
|
854
858
|
["sql-chart", panel.id, sql, panel.source],
|
|
855
859
|
sql,
|
|
856
860
|
panel.source,
|
|
@@ -859,7 +863,14 @@ export function SqlChart({
|
|
|
859
863
|
);
|
|
860
864
|
|
|
861
865
|
const rawRows = result?.rows ?? [];
|
|
862
|
-
const
|
|
866
|
+
const queryErrorMessage =
|
|
867
|
+
queryError instanceof Error
|
|
868
|
+
? queryError.message
|
|
869
|
+
: queryError
|
|
870
|
+
? String(queryError)
|
|
871
|
+
: undefined;
|
|
872
|
+
const error =
|
|
873
|
+
rawRows.length === 0 ? (result?.error ?? queryErrorMessage) : undefined;
|
|
863
874
|
|
|
864
875
|
const { rows, forcedYKeys } = useMemo(() => {
|
|
865
876
|
if (panel.config?.pivot && rawRows.length) {
|
|
@@ -347,6 +347,7 @@ const enUS = {
|
|
|
347
347
|
sectionOptions: "Section options",
|
|
348
348
|
panelOptions: "Panel options",
|
|
349
349
|
fullScreen: "Full screen",
|
|
350
|
+
refresh: "Refresh",
|
|
350
351
|
downloadCsv: "Download CSV",
|
|
351
352
|
viewSql: "View SQL",
|
|
352
353
|
dragToReorder: "Drag to reorder",
|
|
@@ -4969,6 +4970,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
4969
4970
|
failedToSave: "保存失败",
|
|
4970
4971
|
formatSql: "格式 SQL",
|
|
4971
4972
|
fullScreen: "全屏",
|
|
4973
|
+
refresh: "刷新",
|
|
4972
4974
|
hiddenDescription:
|
|
4973
4975
|
"该仪表板在常规列表中是隐藏的。它仍然可以通过直接链接搜索和打开。",
|
|
4974
4976
|
noActiveFilters: "当前没有活动的过滤器 - 此视图将应用默认过滤器状态。",
|
|
@@ -5142,6 +5144,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
5142
5144
|
failedToSave: "No se pudo guardar",
|
|
5143
5145
|
formatSql: "Formato SQL",
|
|
5144
5146
|
fullScreen: "Pantalla completa",
|
|
5147
|
+
refresh: "Actualizar",
|
|
5145
5148
|
hiddenDescription:
|
|
5146
5149
|
"Este panel está oculto de las listas habituales. Se puede buscar y abrir mediante enlace directo.",
|
|
5147
5150
|
noActiveFilters:
|
|
@@ -5318,6 +5321,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
5318
5321
|
failedToSave: "Échec de l'enregistrement",
|
|
5319
5322
|
formatSql: "Format SQL",
|
|
5320
5323
|
fullScreen: "Plein écran",
|
|
5324
|
+
refresh: "Actualiser",
|
|
5321
5325
|
hiddenDescription:
|
|
5322
5326
|
"Ce tableau de bord est masqué dans les listes régulières. Il reste consultable et ouvrable par lien direct.",
|
|
5323
5327
|
noActiveFilters:
|
|
@@ -5495,6 +5499,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
5495
5499
|
failedToSave: "Speichern fehlgeschlagen",
|
|
5496
5500
|
formatSql: "Formatieren Sie SQL",
|
|
5497
5501
|
fullScreen: "Vollbild",
|
|
5502
|
+
refresh: "Aktualisieren",
|
|
5498
5503
|
hiddenDescription:
|
|
5499
5504
|
"Dieses Dashboard ist vor regulären Listen verborgen. Es bleibt durchsuchbar und kann über einen direkten Link geöffnet werden.",
|
|
5500
5505
|
noActiveFilters:
|
|
@@ -5667,6 +5672,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
5667
5672
|
failedToSave: "保存に失敗しました",
|
|
5668
5673
|
formatSql: "形式 SQL",
|
|
5669
5674
|
fullScreen: "全画面表示",
|
|
5675
|
+
refresh: "更新",
|
|
5670
5676
|
hiddenDescription:
|
|
5671
5677
|
"このダッシュボードは通常のリストには表示されません。引き続き検索可能で、直接リンクで開くことができます。",
|
|
5672
5678
|
noActiveFilters:
|
|
@@ -5838,6 +5844,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
5838
5844
|
failedToSave: "저장하지 못했습니다.",
|
|
5839
5845
|
formatSql: "SQL 형식",
|
|
5840
5846
|
fullScreen: "전체 화면",
|
|
5847
|
+
refresh: "새로고침",
|
|
5841
5848
|
hiddenDescription:
|
|
5842
5849
|
"이 대시보드는 일반 목록에서 숨겨져 있습니다. 직접 링크를 통해 검색 및 열 수 있습니다.",
|
|
5843
5850
|
noActiveFilters:
|
|
@@ -6013,6 +6020,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
6013
6020
|
failedToSave: "Falha ao salvar",
|
|
6014
6021
|
formatSql: "Formato SQL",
|
|
6015
6022
|
fullScreen: "Tela cheia",
|
|
6023
|
+
refresh: "Atualizar",
|
|
6016
6024
|
hiddenDescription:
|
|
6017
6025
|
"Este painel está oculto nas listas regulares. Ele permanece pesquisável e aberto por link direto.",
|
|
6018
6026
|
noActiveFilters:
|
|
@@ -6184,6 +6192,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
6184
6192
|
failedToSave: "सहेजने में विफल",
|
|
6185
6193
|
formatSql: "प्रारूप SQL",
|
|
6186
6194
|
fullScreen: "पूर्ण स्क्रीन",
|
|
6195
|
+
refresh: "रीफ़्रेश करें",
|
|
6187
6196
|
hiddenDescription:
|
|
6188
6197
|
"यह डैशबोर्ड नियमित सूचियों से छिपा हुआ है. यह सीधे लिंक द्वारा खोजने योग्य और खोलने योग्य रहता है।",
|
|
6189
6198
|
noActiveFilters:
|
|
@@ -6355,6 +6364,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
6355
6364
|
failedToSave: "فشل الحفظ",
|
|
6356
6365
|
formatSql: "تنسيق SQL",
|
|
6357
6366
|
fullScreen: "شاشة كاملة",
|
|
6367
|
+
refresh: "تحديث",
|
|
6358
6368
|
hiddenDescription:
|
|
6359
6369
|
"لوحة التحكم هذه مخفية من القوائم العادية. يظل قابلاً للبحث والفتح عن طريق الرابط المباشر.",
|
|
6360
6370
|
noActiveFilters:
|