@agent-native/core-corpus 0.209.1 → 0.209.2-nightly-20261016005836
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/dashboard-management/SKILL.md +7 -0
- package/corpus/templates/analytics/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +48 -4
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +16 -3
- package/corpus/templates/analytics/app/i18n/en-US.ts +10 -0
- package/corpus/templates/analytics/app/lib/personal-column-widths.ts +167 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +19 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +139 -38
- package/corpus/templates/analytics/changelog/2026-10-14-dashboards-open-in-view-mode-so-looking-around-never-changes.md +6 -0
- package/corpus/templates/assets/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/brain/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/calendar/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/chat/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +11 -1
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +21 -2
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +11 -1
- package/corpus/templates/clips/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/clips/AGENTS.md +1 -0
- package/corpus/templates/clips/actions/apply-rewind-extension.ts +6 -1
- package/corpus/templates/clips/actions/burn-recording-redactions.ts +11 -4
- package/corpus/templates/clips/actions/create-recording.ts +184 -42
- package/corpus/templates/clips/actions/delete-recording-permanent.ts +87 -5
- package/corpus/templates/clips/actions/finalize-recording.ts +435 -36
- package/corpus/templates/clips/actions/get-recording-app-activity.ts +78 -0
- package/corpus/templates/clips/actions/get-recording-player-data.ts +8 -1
- package/corpus/templates/clips/actions/get-video-storage-status.ts +17 -0
- package/corpus/templates/clips/actions/import-loom-recording.ts +391 -84
- package/corpus/templates/clips/actions/lib/direct-video.ts +2 -1
- package/corpus/templates/clips/actions/lib/ensure-recording-filmstrip.ts +10 -3
- package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +41 -7
- package/corpus/templates/clips/actions/lib/loom-import-job.ts +298 -43
- package/corpus/templates/clips/actions/lib/loom-video.ts +15 -2
- package/corpus/templates/clips/actions/lib/recording-audio-source.ts +20 -0
- package/corpus/templates/clips/actions/lib/video-download-limits.ts +30 -8
- package/corpus/templates/clips/actions/list-recordings.ts +2 -0
- package/corpus/templates/clips/actions/request-transcript.ts +3 -0
- package/corpus/templates/clips/actions/save-recording-app-activity.ts +101 -0
- package/corpus/templates/clips/actions/set-thumbnail.ts +45 -4
- package/corpus/templates/clips/app/components/dictate/vocabulary-section.tsx +17 -6
- package/corpus/templates/clips/app/components/library/library-layout.tsx +4 -0
- package/corpus/templates/clips/app/components/library/recording-card.tsx +10 -2
- package/corpus/templates/clips/app/components/player/browser-diagnostics-panel.tsx +260 -37
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +7 -2
- package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +11 -23
- package/corpus/templates/clips/app/hooks/use-library.ts +1 -0
- package/corpus/templates/clips/app/hooks/use-video-storage-status.ts +113 -13
- package/corpus/templates/clips/app/i18n/en-US.ts +23 -0
- package/corpus/templates/clips/app/lib/local-recording-upload.ts +6 -0
- package/corpus/templates/clips/app/lib/upload-target.ts +42 -4
- package/corpus/templates/clips/app/routes/_app.dictate.tsx +1 -1
- package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +33 -2
- package/corpus/templates/clips/app/routes/import.tsx +12 -10
- package/corpus/templates/clips/app/routes/record.tsx +187 -52
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +13 -2
- package/corpus/templates/clips/changelog/2026-10-14-clips-recognizes-existing-sign-ins-when-checking-access-to-f.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-14-extension-recordings-of-agent-native-apps-include-what-the-a.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-14-fixed-hosted-clips-uploads-failing-when-streamed-chunks-reac.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-14-record-and-store-your-first-three-clips-before-connecting-yo.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-14-record-three-clips-before-connecting-your-own-storage.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-15-the-dictionary-scrolls-through-long-lists-and-stays-closed-d.md +6 -0
- package/corpus/templates/clips/chrome-extension/PERMISSIONS.md +18 -1
- package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +4 -1
- package/corpus/templates/clips/chrome-extension/src/app-activity.ts +278 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +121 -13
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +47 -0
- package/corpus/templates/clips/chrome-extension/src/options.html +2 -1
- package/corpus/templates/clips/chrome-extension/src/popup.ts +50 -11
- package/corpus/templates/clips/chrome-extension/src/recording-duration.ts +29 -0
- package/corpus/templates/clips/desktop/src/app.tsx +99 -43
- package/corpus/templates/clips/desktop/src/lib/clip-duration-limit.ts +44 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -1
- package/corpus/templates/clips/scripts/takedown-hosted-recording.ts +340 -0
- package/corpus/templates/clips/server/db/schema.ts +46 -1
- package/corpus/templates/clips/server/lib/app-activity.ts +79 -0
- package/corpus/templates/clips/server/lib/ensure-recording-thumbnail.ts +24 -10
- package/corpus/templates/clips/server/lib/hosted-clip-allowance.ts +1203 -0
- package/corpus/templates/clips/server/lib/hosted-clip-disposable-domains.ts +1010 -0
- package/corpus/templates/clips/server/lib/initial-tool-names.ts +1 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +133 -15
- package/corpus/templates/clips/server/lib/recording-failures.ts +54 -0
- package/corpus/templates/clips/server/lib/recording-media-cleanup.ts +157 -7
- package/corpus/templates/clips/server/lib/resumable-upload-cleanup.ts +40 -1
- package/corpus/templates/clips/server/lib/resumable-upload-provider.ts +8 -0
- package/corpus/templates/clips/server/lib/s3-upload-provider.ts +309 -205
- package/corpus/templates/clips/server/lib/upload-lease.ts +330 -6
- package/corpus/templates/clips/server/lib/upload-recording-media.ts +161 -0
- package/corpus/templates/clips/server/lib/video-storage.ts +619 -2
- package/corpus/templates/clips/server/plugins/auth.ts +1 -0
- package/corpus/templates/clips/server/plugins/config.ts +1 -0
- package/corpus/templates/clips/server/plugins/db.ts +42 -0
- package/corpus/templates/clips/server/plugins/feature-flags.ts +5 -2
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +5 -1
- package/corpus/templates/clips/server/routes/api/agent-app-activity.json.get.ts +134 -0
- package/corpus/templates/clips/server/routes/api/agent-context.json.get.ts +6 -0
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +8 -1
- package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +50 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +35 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +317 -39
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +45 -5
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/resume.get.ts +15 -1
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +26 -2
- package/corpus/templates/clips/shared/agent-context.ts +8 -0
- package/corpus/templates/clips/shared/app-activity.ts +325 -0
- package/corpus/templates/clips/shared/browser-diagnostics.ts +43 -0
- package/corpus/templates/clips/shared/feature-flags.ts +13 -1
- package/corpus/templates/clips/shared/recording-core.ts +6 -3
- package/corpus/templates/content/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/crm/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/design/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/design/AGENTS.md +1 -0
- package/corpus/templates/design/CHANGELOG.md +6 -0
- package/corpus/templates/design/actions/delete-file.ts +5 -0
- package/corpus/templates/design/actions/get-motion-timeline.ts +33 -20
- package/corpus/templates/design/app/components/design/AddLocalhostScreenDialog.tsx +49 -9
- package/corpus/templates/design/app/components/design/EditPanel.tsx +10 -0
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +2 -0
- package/corpus/templates/design/changelog/2026-10-15-empty-inspector-section-titles-stay-compact.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-external-design-agents-can-delete-screens-when-you-ask-them.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-fixed-local-screen-account-and-storage-setup-when-route-or-c.md +6 -0
- package/corpus/templates/design/e2e/rails.ts +54 -14
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/design/shared/motion-source-read.ts +1 -0
- package/corpus/templates/dispatch/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +2 -2
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +31 -0
- package/corpus/templates/forms/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/mail/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/plan/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/slides/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/slides/app/components/editor/bullet-editing.ts +4 -0
- package/corpus/templates/slides/app/components/editor/in-place-text-session.browser-cases.json +983 -0
- package/corpus/templates/slides/app/components/editor/in-place-text-session.browser-host.ts +747 -0
- package/corpus/templates/slides/app/components/editor/in-place-text-session.ts +1273 -246
- package/corpus/templates/slides/app/components/editor/list-editing.ts +4 -1
- package/corpus/templates/slides/app/components/editor/rte-test-utils.ts +265 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +2 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +33 -23
- package/corpus/templates/slides/app/pages/Index.tsx +4 -1
- package/corpus/templates/slides/changelog/2026-10-15-pasting-into-slide-text-keeps-lists-links-and-formatting.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-15-select-all-and-typing-after-style-toggles-work-first-time.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-15-slide-text-editing-matches-standard-rich-text-editors.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-15-slides-keeps-your-original-request-visible-when-a-deck-canno.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-15-slides-shows-fallback-prompts-immediately-and-persists-gener.md +1 -1
- package/corpus/templates/slides/interaction-parity.md +31 -0
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -62,6 +62,13 @@ current state first, then syncs open dashboard editors.
|
|
|
62
62
|
Saved analyses follow the same undo model with `analysis_revisions`,
|
|
63
63
|
`list-analysis-revisions`, and `restore-analysis-revision`.
|
|
64
64
|
|
|
65
|
+
Dashboards open in View mode. An editor switches the header toggle to Edit
|
|
66
|
+
before drags, renames, and column edits on the page save for everyone; the chat
|
|
67
|
+
context reports the current mode. A table column width dragged in View mode is
|
|
68
|
+
stored only in that person's browser, so "widen this column for me" is a drag
|
|
69
|
+
in View mode, not a dashboard change. `config.columns[].width` is the shared
|
|
70
|
+
width everyone sees; set it only when asked to change the dashboard itself.
|
|
71
|
+
|
|
65
72
|
Never use `db-patch`, raw SQL, or settings-key edits to create or modify a
|
|
66
73
|
dashboard config. Those bypass the dashboard action's access checks, SQL
|
|
67
74
|
validation, collab sync, and verification. If a dashboard action fails
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// AUTO-GENERATED by @agent-native/core deploy build
|
|
2
|
+
import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
|
|
3
|
+
|
|
4
|
+
const pluginStems = ["agent-chat","analytics-alert-jobs","analytics-rollup-backfill-jobs","auth","collab","config","core-routes","creative-context","dashboard-report-jobs","db","feature-flags","labs","onboarding","otel","session-replay-retention-jobs","transactional-emails","uptime-monitor-jobs"];
|
|
5
|
+
|
|
6
|
+
export default function markBuildDiscoveredPlugins(nitroApp) {
|
|
7
|
+
for (const stem of pluginStems) {
|
|
8
|
+
markDefaultPluginProvided(nitroApp, stem);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1221,6 +1221,13 @@ interface SqlChartProps {
|
|
|
1221
1221
|
onCopyTableChange?: (handler: (() => Promise<void>) | null) => void;
|
|
1222
1222
|
/** Passing this makes a table's columns editable; omit it for read-only tables. */
|
|
1223
1223
|
onSaveColumns?: (columns: TableColumnConfig[]) => void;
|
|
1224
|
+
/** One viewer's own column widths, drawn over the panel's saved ones. */
|
|
1225
|
+
columnWidths?: Record<string, number>;
|
|
1226
|
+
/**
|
|
1227
|
+
* Passing this lets a viewer drag column widths for themselves; the drag
|
|
1228
|
+
* calls this instead of `onSaveColumns`, and null resets the column.
|
|
1229
|
+
*/
|
|
1230
|
+
onColumnWidthChange?: (columnKey: string, width: number | null) => void;
|
|
1224
1231
|
dashboardId?: string;
|
|
1225
1232
|
extensionContext?: Record<string, unknown> | null;
|
|
1226
1233
|
}
|
|
@@ -1238,6 +1245,8 @@ export function SqlChart({
|
|
|
1238
1245
|
onExportCsvChange,
|
|
1239
1246
|
onCopyTableChange,
|
|
1240
1247
|
onSaveColumns,
|
|
1248
|
+
columnWidths,
|
|
1249
|
+
onColumnWidthChange,
|
|
1241
1250
|
dashboardId,
|
|
1242
1251
|
extensionContext,
|
|
1243
1252
|
}: SqlChartProps) {
|
|
@@ -1469,6 +1478,8 @@ export function SqlChart({
|
|
|
1469
1478
|
onExportCsvChange={onExportCsvChange}
|
|
1470
1479
|
onCopyTableChange={onCopyTableChange}
|
|
1471
1480
|
onSaveColumns={onSaveColumns}
|
|
1481
|
+
columnWidths={columnWidths}
|
|
1482
|
+
onColumnWidthChange={onColumnWidthChange}
|
|
1472
1483
|
/>,
|
|
1473
1484
|
);
|
|
1474
1485
|
}
|
|
@@ -1936,20 +1947,26 @@ function TableRenderer({
|
|
|
1936
1947
|
onExportCsvChange,
|
|
1937
1948
|
onCopyTableChange,
|
|
1938
1949
|
onSaveColumns,
|
|
1950
|
+
columnWidths,
|
|
1951
|
+
onColumnWidthChange,
|
|
1939
1952
|
}: {
|
|
1940
1953
|
rows: Record<string, unknown>[];
|
|
1941
1954
|
panel: SqlPanel;
|
|
1942
1955
|
onExportCsvChange?: (handler: (() => void) | null) => void;
|
|
1943
1956
|
onCopyTableChange?: (handler: (() => Promise<void>) | null) => void;
|
|
1944
1957
|
onSaveColumns?: (columns: TableColumnConfig[]) => void;
|
|
1958
|
+
columnWidths?: Record<string, number>;
|
|
1959
|
+
onColumnWidthChange?: (columnKey: string, width: number | null) => void;
|
|
1945
1960
|
}) {
|
|
1946
1961
|
const t = useT();
|
|
1947
1962
|
const config = panel.config;
|
|
1948
1963
|
const sortable = config?.sortable !== false;
|
|
1949
1964
|
const editable = onSaveColumns !== undefined;
|
|
1965
|
+
const personalResize = onColumnWidthChange !== undefined;
|
|
1966
|
+
const resizable = editable || personalResize;
|
|
1950
1967
|
const rowKeys = useMemo(() => new Set(Object.keys(rows[0] ?? {})), [rows]);
|
|
1951
1968
|
|
|
1952
|
-
const
|
|
1969
|
+
const savedColumns = useMemo<TableColumnConfig[]>(() => {
|
|
1953
1970
|
if (config?.columns?.length) {
|
|
1954
1971
|
const configured = config.columns.filter(
|
|
1955
1972
|
(c) => !c.hidden && rowKeys.has(c.key),
|
|
@@ -1959,6 +1976,18 @@ function TableRenderer({
|
|
|
1959
1976
|
return Object.keys(rows[0]).map((key) => ({ key }));
|
|
1960
1977
|
}, [config?.columns, rows, rowKeys]);
|
|
1961
1978
|
|
|
1979
|
+
// A viewer's own widths are drawn over the saved ones but never saved:
|
|
1980
|
+
// edits start from `editableColumns`, which reads the panel config.
|
|
1981
|
+
const columns = useMemo<TableColumnConfig[]>(
|
|
1982
|
+
() =>
|
|
1983
|
+
columnWidths
|
|
1984
|
+
? savedColumns.map((c) =>
|
|
1985
|
+
columnWidths[c.key] ? { ...c, width: columnWidths[c.key] } : c,
|
|
1986
|
+
)
|
|
1987
|
+
: savedColumns,
|
|
1988
|
+
[savedColumns, columnWidths],
|
|
1989
|
+
);
|
|
1990
|
+
|
|
1962
1991
|
// Edits start from the persisted list, hidden entries included, so a change
|
|
1963
1992
|
// never drops a key the agent configured. Visible keys missing from it (the
|
|
1964
1993
|
// all-keys fallback above) are appended so every visible column is editable.
|
|
@@ -2060,7 +2089,9 @@ function TableRenderer({
|
|
|
2060
2089
|
MIN_TABLE_COLUMN_WIDTH,
|
|
2061
2090
|
Math.round(resize.startWidth + event.clientX - resize.startX),
|
|
2062
2091
|
);
|
|
2063
|
-
if (width
|
|
2092
|
+
if (width === resize.startWidth) return;
|
|
2093
|
+
if (personalResize) onColumnWidthChange(resize.key, width);
|
|
2094
|
+
else updateColumn(resize.key, { width });
|
|
2064
2095
|
};
|
|
2065
2096
|
|
|
2066
2097
|
const renderHeaderCell = (col: TableColumnConfig) => {
|
|
@@ -2112,7 +2143,7 @@ function TableRenderer({
|
|
|
2112
2143
|
}
|
|
2113
2144
|
className={`text-left align-bottom py-1.5 px-2 font-medium text-muted-foreground ${
|
|
2114
2145
|
sortable ? "cursor-pointer select-none hover:text-foreground" : ""
|
|
2115
|
-
} ${
|
|
2146
|
+
} ${resizable ? "relative" : ""} ${width ? "overflow-hidden text-ellipsis whitespace-nowrap" : ""}`}
|
|
2116
2147
|
onClick={(event) => {
|
|
2117
2148
|
// The second click of a double-click renames the column and must not
|
|
2118
2149
|
// toggle the sort a second time.
|
|
@@ -2165,18 +2196,31 @@ function TableRenderer({
|
|
|
2165
2196
|
</>
|
|
2166
2197
|
)}
|
|
2167
2198
|
</span>
|
|
2168
|
-
{
|
|
2199
|
+
{resizable &&
|
|
2169
2200
|
!isRenaming &&
|
|
2170
2201
|
col.key !== columns[columns.length - 1]?.key ? (
|
|
2171
2202
|
<span
|
|
2172
2203
|
role="separator"
|
|
2173
2204
|
aria-orientation="vertical"
|
|
2174
2205
|
aria-label={t("sqlDashboard.columnResize")}
|
|
2206
|
+
title={
|
|
2207
|
+
personalResize
|
|
2208
|
+
? t("dashboardMode.columnResizePersonal")
|
|
2209
|
+
: t("dashboardMode.columnResizeShared")
|
|
2210
|
+
}
|
|
2175
2211
|
className="absolute inset-y-0 end-0 w-1.5 cursor-col-resize hover:bg-border"
|
|
2176
2212
|
onPointerDown={(event) => handleResizeStart(col.key, event)}
|
|
2177
2213
|
onPointerMove={handleResizeMove}
|
|
2178
2214
|
onPointerUp={handleResizeEnd}
|
|
2179
2215
|
onClick={(event) => event.stopPropagation()}
|
|
2216
|
+
onDoubleClick={
|
|
2217
|
+
personalResize
|
|
2218
|
+
? (event) => {
|
|
2219
|
+
event.stopPropagation();
|
|
2220
|
+
onColumnWidthChange(col.key, null);
|
|
2221
|
+
}
|
|
2222
|
+
: undefined
|
|
2223
|
+
}
|
|
2180
2224
|
/>
|
|
2181
2225
|
) : null}
|
|
2182
2226
|
</th>
|
|
@@ -26,6 +26,7 @@ export interface DashboardChatContextArgs {
|
|
|
26
26
|
title?: string | null;
|
|
27
27
|
panelCount?: number;
|
|
28
28
|
canEdit?: boolean;
|
|
29
|
+
editing?: boolean;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export interface DashboardPanelChatContextArgs {
|
|
@@ -56,6 +57,7 @@ function dashboardContext(
|
|
|
56
57
|
title: string;
|
|
57
58
|
panelCount?: number;
|
|
58
59
|
canEdit?: boolean;
|
|
60
|
+
editing?: boolean;
|
|
59
61
|
},
|
|
60
62
|
): string {
|
|
61
63
|
const lines = [
|
|
@@ -69,6 +71,13 @@ function dashboardContext(
|
|
|
69
71
|
if (typeof args.canEdit === "boolean") {
|
|
70
72
|
lines.push(`User can edit: ${args.canEdit ? "yes" : "no"}`);
|
|
71
73
|
}
|
|
74
|
+
if (args.canEdit && typeof args.editing === "boolean") {
|
|
75
|
+
lines.push(
|
|
76
|
+
args.editing
|
|
77
|
+
? "Dashboard mode: edit (the user's own drags and renames save for everyone)"
|
|
78
|
+
: "Dashboard mode: view (moving or editing panels, renaming, and undo on the page wait for edit mode, and the user's column drags stay in their browser; saving a view, restoring history, archiving, deleting, and adding a first panel still work and apply to everyone)",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
72
81
|
if (typeof window !== "undefined") {
|
|
73
82
|
lines.push(
|
|
74
83
|
`Current URL: ${window.location.pathname}${window.location.search}`,
|
|
@@ -115,7 +124,7 @@ function isAgentSidebarOpenInDocument(): boolean {
|
|
|
115
124
|
export function useDashboardChatContext(
|
|
116
125
|
args: DashboardChatContextArgs,
|
|
117
126
|
): DashboardChatContextResult {
|
|
118
|
-
const { id, kind, title, panelCount, canEdit } = args;
|
|
127
|
+
const { id, kind, title, panelCount, canEdit, editing } = args;
|
|
119
128
|
const [isAgentSidebarOpen, setIsAgentSidebarOpen] = useState(
|
|
120
129
|
isAgentSidebarOpenInDocument,
|
|
121
130
|
);
|
|
@@ -249,13 +258,14 @@ export function useDashboardChatContext(
|
|
|
249
258
|
title: displayTitle,
|
|
250
259
|
panelCount,
|
|
251
260
|
canEdit,
|
|
261
|
+
editing,
|
|
252
262
|
}),
|
|
253
263
|
openSidebar: false,
|
|
254
264
|
focus: false,
|
|
255
265
|
});
|
|
256
266
|
}, CONTEXT_PUBLISH_DELAY_MS);
|
|
257
267
|
return () => clearTimeout(timer);
|
|
258
|
-
}, [canEdit, id, kind, panelCount, title]);
|
|
268
|
+
}, [canEdit, editing, id, kind, panelCount, title]);
|
|
259
269
|
|
|
260
270
|
useEffect(() => {
|
|
261
271
|
if (!id || hasSelectedPanel) return;
|
|
@@ -268,6 +278,9 @@ export function useDashboardChatContext(
|
|
|
268
278
|
title: displayTitle,
|
|
269
279
|
panelCount,
|
|
270
280
|
canEdit,
|
|
281
|
+
...(canEdit && typeof editing === "boolean"
|
|
282
|
+
? { mode: editing ? "edit" : "view" }
|
|
283
|
+
: {}),
|
|
271
284
|
[SELECTED_OBJECT_SOURCE_FIELD]: TAB_ID,
|
|
272
285
|
};
|
|
273
286
|
const timer = setTimeout(() => {
|
|
@@ -279,7 +292,7 @@ export function useDashboardChatContext(
|
|
|
279
292
|
publishedSelectionRef.current = selection;
|
|
280
293
|
}, CONTEXT_PUBLISH_DELAY_MS);
|
|
281
294
|
return () => clearTimeout(timer);
|
|
282
|
-
}, [canEdit, hasSelectedPanel, id, kind, panelCount, title]);
|
|
295
|
+
}, [canEdit, editing, hasSelectedPanel, id, kind, panelCount, title]);
|
|
283
296
|
|
|
284
297
|
useEffect(() => {
|
|
285
298
|
if (!id) return;
|
|
@@ -167,6 +167,16 @@ export default {
|
|
|
167
167
|
templateCatalog: "Catalog",
|
|
168
168
|
semanticLayer: "Semantic Layer",
|
|
169
169
|
},
|
|
170
|
+
dashboardMode: {
|
|
171
|
+
label: "Dashboard mode",
|
|
172
|
+
view: "View",
|
|
173
|
+
edit: "Edit",
|
|
174
|
+
viewHint:
|
|
175
|
+
"Look without changing anything. Column widths you drag stay yours.",
|
|
176
|
+
editHint: "Changes save for everyone",
|
|
177
|
+
columnResizePersonal: "Drag to resize for you only. Double-click to reset.",
|
|
178
|
+
columnResizeShared: "Drag to resize for everyone",
|
|
179
|
+
},
|
|
170
180
|
dashboardHeader: {
|
|
171
181
|
updated: "Updated {{date}}",
|
|
172
182
|
created: "Created {{date}}",
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import type { TableColumnConfig } from "@/pages/adhoc/sql-dashboard/types";
|
|
4
|
+
|
|
5
|
+
const STORAGE_PREFIX = "analytics-column-widths:v1:";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A width someone dragged while viewing a dashboard, kept in their browser.
|
|
9
|
+
* `base` is the dashboard's own width for the column at the time (null when it
|
|
10
|
+
* had none): once the dashboard's width changes, the personal one is dropped,
|
|
11
|
+
* so a later edit for everyone is never hidden behind an old personal drag.
|
|
12
|
+
*/
|
|
13
|
+
export interface PersonalColumnWidth {
|
|
14
|
+
width: number;
|
|
15
|
+
base: number | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type PersonalColumnWidths = Record<string, PersonalColumnWidth>;
|
|
19
|
+
|
|
20
|
+
export function personalColumnWidthsKey(
|
|
21
|
+
dashboardId: string,
|
|
22
|
+
panelId: string,
|
|
23
|
+
): string {
|
|
24
|
+
return `${STORAGE_PREFIX}${dashboardId}:${panelId}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isPersonalColumnWidth(value: unknown): value is PersonalColumnWidth {
|
|
28
|
+
if (!value || typeof value !== "object") return false;
|
|
29
|
+
const entry = value as Record<string, unknown>;
|
|
30
|
+
return (
|
|
31
|
+
typeof entry.width === "number" &&
|
|
32
|
+
Number.isFinite(entry.width) &&
|
|
33
|
+
entry.width > 0 &&
|
|
34
|
+
(entry.base === null ||
|
|
35
|
+
(typeof entry.base === "number" && Number.isFinite(entry.base)))
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Parses a stored value, keeping only well-formed entries. */
|
|
40
|
+
export function parsePersonalColumnWidths(
|
|
41
|
+
raw: string | null,
|
|
42
|
+
): PersonalColumnWidths {
|
|
43
|
+
if (!raw) return {};
|
|
44
|
+
let parsed: unknown;
|
|
45
|
+
try {
|
|
46
|
+
parsed = JSON.parse(raw);
|
|
47
|
+
} catch {
|
|
48
|
+
// coercion-ok: a corrupt entry means no personal widths; the dashboard's own widths apply.
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
const widths: PersonalColumnWidths = {};
|
|
55
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
56
|
+
if (isPersonalColumnWidth(value)) widths[key] = value;
|
|
57
|
+
}
|
|
58
|
+
return widths;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The stored entries still based on the dashboard's current widths. */
|
|
62
|
+
export function currentPersonalColumnWidths(
|
|
63
|
+
stored: PersonalColumnWidths,
|
|
64
|
+
columns: TableColumnConfig[] | undefined,
|
|
65
|
+
): PersonalColumnWidths {
|
|
66
|
+
const sharedWidth = new Map(
|
|
67
|
+
(columns ?? []).map((column) => [column.key, column.width ?? null]),
|
|
68
|
+
);
|
|
69
|
+
const current: PersonalColumnWidths = {};
|
|
70
|
+
for (const [key, entry] of Object.entries(stored)) {
|
|
71
|
+
if ((sharedWidth.get(key) ?? null) === entry.base) current[key] = entry;
|
|
72
|
+
}
|
|
73
|
+
return current;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The personal widths that apply against the dashboard's current widths. */
|
|
77
|
+
export function activePersonalColumnWidths(
|
|
78
|
+
stored: PersonalColumnWidths,
|
|
79
|
+
columns: TableColumnConfig[] | undefined,
|
|
80
|
+
): Record<string, number> {
|
|
81
|
+
const active: Record<string, number> = {};
|
|
82
|
+
for (const [key, entry] of Object.entries(
|
|
83
|
+
currentPersonalColumnWidths(stored, columns),
|
|
84
|
+
)) {
|
|
85
|
+
active[key] = entry.width;
|
|
86
|
+
}
|
|
87
|
+
return active;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function readStoredWidths(key: string | null): PersonalColumnWidths {
|
|
91
|
+
if (!key || typeof window === "undefined") return {};
|
|
92
|
+
let raw: string | null;
|
|
93
|
+
try {
|
|
94
|
+
raw = window.localStorage.getItem(key);
|
|
95
|
+
} catch {
|
|
96
|
+
// coercion-ok: storage blocked by the browser; widths then last for this visit only.
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
return parsePersonalColumnWidths(raw);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function writeStoredWidths(key: string, widths: PersonalColumnWidths): void {
|
|
103
|
+
try {
|
|
104
|
+
if (Object.keys(widths).length === 0) window.localStorage.removeItem(key);
|
|
105
|
+
else window.localStorage.setItem(key, JSON.stringify(widths));
|
|
106
|
+
} catch {
|
|
107
|
+
// coercion-ok: storage blocked or full; the width still applies until the page reloads.
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Column widths one person dragged on one dashboard table. They live in this
|
|
113
|
+
* browser only and never change the dashboard for anyone else.
|
|
114
|
+
*/
|
|
115
|
+
export function usePersonalColumnWidths(
|
|
116
|
+
dashboardId: string | undefined,
|
|
117
|
+
panelId: string,
|
|
118
|
+
columns: TableColumnConfig[] | undefined,
|
|
119
|
+
): {
|
|
120
|
+
widths: Record<string, number>;
|
|
121
|
+
setWidth: (columnKey: string, width: number | null) => void;
|
|
122
|
+
} {
|
|
123
|
+
const key = dashboardId
|
|
124
|
+
? personalColumnWidthsKey(dashboardId, panelId)
|
|
125
|
+
: null;
|
|
126
|
+
const [stored, setStored] = useState<PersonalColumnWidths>(() =>
|
|
127
|
+
readStoredWidths(key),
|
|
128
|
+
);
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
setStored(readStoredWidths(key));
|
|
131
|
+
}, [key]);
|
|
132
|
+
// Hiding a stale width is not enough: kept in storage, it would come back
|
|
133
|
+
// if the dashboard's width returned to its base and hide that newer change.
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (!key) return;
|
|
136
|
+
setStored((current) => {
|
|
137
|
+
const kept = currentPersonalColumnWidths(current, columns);
|
|
138
|
+
if (Object.keys(kept).length === Object.keys(current).length) {
|
|
139
|
+
return current;
|
|
140
|
+
}
|
|
141
|
+
writeStoredWidths(key, kept);
|
|
142
|
+
return kept;
|
|
143
|
+
});
|
|
144
|
+
}, [columns, key]);
|
|
145
|
+
|
|
146
|
+
const setWidth = useCallback(
|
|
147
|
+
(columnKey: string, width: number | null) => {
|
|
148
|
+
if (!key) return;
|
|
149
|
+
const base =
|
|
150
|
+
columns?.find((column) => column.key === columnKey)?.width ?? null;
|
|
151
|
+
setStored((current) => {
|
|
152
|
+
const next = { ...current };
|
|
153
|
+
if (width === null) delete next[columnKey];
|
|
154
|
+
else next[columnKey] = { width, base };
|
|
155
|
+
writeStoredWidths(key, next);
|
|
156
|
+
return next;
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
[columns, key],
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const widths = useMemo(
|
|
163
|
+
() => activePersonalColumnWidths(stored, columns),
|
|
164
|
+
[stored, columns],
|
|
165
|
+
);
|
|
166
|
+
return { widths, setWidth };
|
|
167
|
+
}
|
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
} from "@/components/ui/tooltip";
|
|
60
60
|
import type { SelectDashboardPanelOptions } from "@/hooks/use-dashboard-chat-context";
|
|
61
61
|
import { buildCustomBlockPromotionRequest } from "@/lib/custom-block-promotion";
|
|
62
|
+
import { usePersonalColumnWidths } from "@/lib/personal-column-widths";
|
|
62
63
|
import { cn } from "@/lib/utils";
|
|
63
64
|
|
|
64
65
|
import { timeRangeDays } from "./pivot";
|
|
@@ -164,6 +165,22 @@ export function SqlChartCard({
|
|
|
164
165
|
const exportToGoogleSheets = useActionMutation(
|
|
165
166
|
"export-dashboard-panel-to-google-sheet",
|
|
166
167
|
);
|
|
168
|
+
// While viewing, dragging a column edge sizes it for this person only. A
|
|
169
|
+
// table offers rename, reorder, and format whenever it gets onSaveColumns,
|
|
170
|
+
// so only edit mode passes it on.
|
|
171
|
+
const personalColumnWidths = usePersonalColumnWidths(
|
|
172
|
+
dashboardId,
|
|
173
|
+
panel.id,
|
|
174
|
+
panel.config?.columns,
|
|
175
|
+
);
|
|
176
|
+
const tableColumnProps = editable
|
|
177
|
+
? { onSaveColumns }
|
|
178
|
+
: dashboardId && panel.chartType === "table"
|
|
179
|
+
? {
|
|
180
|
+
columnWidths: personalColumnWidths.widths,
|
|
181
|
+
onColumnWidthChange: personalColumnWidths.setWidth,
|
|
182
|
+
}
|
|
183
|
+
: {};
|
|
167
184
|
|
|
168
185
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
169
186
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
@@ -811,7 +828,7 @@ export function SqlChartCard({
|
|
|
811
828
|
dashboardId={dashboardId}
|
|
812
829
|
onExportCsvChange={handleExportCsvChange}
|
|
813
830
|
onCopyTableChange={handleCopyTableChange}
|
|
814
|
-
|
|
831
|
+
{...tableColumnProps}
|
|
815
832
|
extensionContext={extensionContext}
|
|
816
833
|
/>
|
|
817
834
|
</CardContent>
|
|
@@ -832,7 +849,7 @@ export function SqlChartCard({
|
|
|
832
849
|
reportScreenshot={reportScreenshot}
|
|
833
850
|
refreshToken={refreshToken}
|
|
834
851
|
onRefreshRequested={handleRefresh}
|
|
835
|
-
|
|
852
|
+
{...tableColumnProps}
|
|
836
853
|
dashboardId={dashboardId}
|
|
837
854
|
extensionContext={extensionContext}
|
|
838
855
|
/>
|