grain-tools 0.10.15 → 0.10.16

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/dist/web-app.html CHANGED
@@ -12820,7 +12820,7 @@ const useAppStore = create$3((set2, get2) => ({
12820
12820
  chartsEnabled: false,
12821
12821
  mcpEnabled: false,
12822
12822
  mcpEntitled: false,
12823
- grainServiceInstalled: true,
12823
+ grainServiceSetup: "registered",
12824
12824
  mcpSafeMode: true,
12825
12825
  activeConnectorType: null,
12826
12826
  activeConnectionId: null,
@@ -12910,7 +12910,7 @@ const useAppStore = create$3((set2, get2) => ({
12910
12910
  "filterResultsByActiveFile",
12911
12911
  "mcpEnabled",
12912
12912
  "mcpEntitled",
12913
- "grainServiceInstalled",
12913
+ "grainServiceSetup",
12914
12914
  "mcpSafeMode",
12915
12915
  "activeConnectorType",
12916
12916
  "activeConnectionId",
@@ -13073,6 +13073,7 @@ const ALL_TOOLS = [
13073
13073
  "list_connections",
13074
13074
  "get_demo_dataset",
13075
13075
  "save_connection",
13076
+ "create_connection_draft",
13076
13077
  "request_connection_credentials",
13077
13078
  "test_connection",
13078
13079
  "delete_connection",
@@ -13170,6 +13171,7 @@ const TELEMETRY_CONNECTORS = [
13170
13171
  ];
13171
13172
  const TELEMETRY_TOOL_NAMES = [...ALL_TOOLS, "unknown"];
13172
13173
  const TELEMETRY_COMMAND_IDS = [
13174
+ "grain.setUpService",
13173
13175
  "grain.showDaemonInfo",
13174
13176
  "grain.mcp.restart",
13175
13177
  "grain.mcp.testConnection",
@@ -13510,6 +13512,8 @@ const TELEMETRY_ERROR_IDS = [
13510
13512
  * Not a database error: nothing was ever sent to a connector.
13511
13513
  */
13512
13514
  "credential_required",
13515
+ "setup_required",
13516
+ "connection_draft_rejected",
13513
13517
  /**
13514
13518
  * The session holding this query's tab was evicted (SessionManager's maxSessions
13515
13519
  * cap) while the query was still running, so the result had nowhere to land.
@@ -13774,7 +13778,24 @@ async function buildGuideHubState() {
13774
13778
  return { ...DEFAULT_GUIDE_HUB_STATE, telemetryEnabled };
13775
13779
  }
13776
13780
  }
13777
- const WEB_SESSION_ID = `web-${crypto.randomUUID()}`;
13781
+ const WEB_SESSION_KEY = "grain:web-session-id";
13782
+ function resolveWebSessionId() {
13783
+ try {
13784
+ const stored = window.sessionStorage.getItem(WEB_SESSION_KEY);
13785
+ if (stored && /^web-[0-9a-f-]{36}$/.test(stored)) return stored;
13786
+ } catch {
13787
+ return `web-${crypto.randomUUID()}`;
13788
+ }
13789
+ return `web-${crypto.randomUUID()}`;
13790
+ }
13791
+ const WEB_SESSION_ID = resolveWebSessionId();
13792
+ function rememberWebSessionId() {
13793
+ try {
13794
+ window.sessionStorage.setItem(WEB_SESSION_KEY, WEB_SESSION_ID);
13795
+ } catch {
13796
+ return;
13797
+ }
13798
+ }
13778
13799
  function resolveProjectedTarget(tabId, payload) {
13779
13800
  const parsed = tabId ? parseProjectedTabId(tabId) : void 0;
13780
13801
  const tab = tabId ? useAppStore.getState().tabs.find((t) => t.id === tabId) : void 0;
@@ -14166,6 +14187,7 @@ function useWebTabSync(options) {
14166
14187
  }
14167
14188
  }
14168
14189
  unreachableStreakRef.current = 0;
14190
+ rememberWebSessionId();
14169
14191
  setDaemonStatus("connected");
14170
14192
  setIsReady(true);
14171
14193
  } catch (err) {
@@ -14214,7 +14236,8 @@ function useWebToolProxy() {
14214
14236
  query: args.sql,
14215
14237
  sessionId,
14216
14238
  connectionId: args.connectionId,
14217
- tabId: args.tabId
14239
+ tabId: args.tabId,
14240
+ title: args.title
14218
14241
  });
14219
14242
  if (!ok) {
14220
14243
  throw new Error(data.error || "Query failed");
@@ -14330,29 +14353,29 @@ function ReauthorizeNotice({
14330
14353
  const [copied, setCopied] = reactExports.useState(false);
14331
14354
  const copyCommand = async () => {
14332
14355
  try {
14333
- await navigator.clipboard.writeText("grain open");
14356
+ await navigator.clipboard.writeText("grain ui");
14334
14357
  setCopied(true);
14335
14358
  } catch {
14336
14359
  setCopied(false);
14337
14360
  }
14338
14361
  };
14339
14362
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-reauth", role: "alert", children: [
14340
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-reauth-title", children: "Grain is running. Reconnect this browser tab." }),
14363
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-reauth-title", children: reason === "unreachable" ? "This tab lost its connection to Grain." : "Grain is running. Reconnect this browser tab." }),
14341
14364
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Run this command in your terminal to open your workspace again:" }),
14342
14365
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-reauth-action", children: [
14343
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "sp-web-reauth-command", children: "grain open" }),
14366
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "sp-web-reauth-command", children: "grain ui" }),
14344
14367
  /* @__PURE__ */ jsxRuntimeExports.jsx(
14345
14368
  "button",
14346
14369
  {
14347
14370
  type: "button",
14348
14371
  className: "sp-web-secondary-button",
14349
14372
  onClick: () => void copyCommand(),
14350
- "aria-label": "Copy grain open",
14373
+ "aria-label": "Copy grain ui",
14351
14374
  children: copied ? "Copied" : "Copy"
14352
14375
  }
14353
14376
  )
14354
14377
  ] }),
14355
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-reauth-detail", children: reason === "expired-link" ? "This link has expired or was already used." : reason === "unreachable" ? "Grain may have restarted since this tab opened." : "This tab needs a fresh workspace link." })
14378
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-reauth-detail", children: reason === "expired-link" ? "This link has expired or was already used." : reason === "unreachable" ? "Grain may have stopped, or restarted on a different port. If it stopped, start it again first." : "This tab needs a fresh workspace link." })
14356
14379
  ] });
14357
14380
  }
14358
14381
  function isUpdateMarkerStatus(value) {
@@ -14472,7 +14495,10 @@ function fieldVisible(field, values) {
14472
14495
  function ConnectionSetupPage({
14473
14496
  onDone,
14474
14497
  onCancel,
14475
- initialConnection
14498
+ initialConnection,
14499
+ actions: actions2,
14500
+ mode = "full",
14501
+ canEditDetails = false
14476
14502
  }) {
14477
14503
  const { callServerTool, isReady } = useHostContext();
14478
14504
  const [connectors, setConnectors] = reactExports.useState([]);
@@ -14489,20 +14515,20 @@ function ConnectionSetupPage({
14489
14515
  initialConnection?.["id"] ?? `conn_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
14490
14516
  )
14491
14517
  );
14518
+ const [displayMode, setDisplayMode] = reactExports.useState(mode);
14492
14519
  reactExports.useEffect(() => {
14493
- if (!isReady) {
14520
+ if (!actions2 && !isReady) {
14494
14521
  return;
14495
14522
  }
14496
14523
  let active = true;
14497
- void callServerTool("list_connectors", {}).then((result) => {
14524
+ void (actions2 ? actions2.listConnectors() : callServerTool("list_connectors", {}).then((result) => {
14525
+ if (result.isError) throw new Error(toolMessage(result));
14526
+ return JSON.parse(toolMessage(result));
14527
+ })).then((result) => {
14498
14528
  if (!active) {
14499
14529
  return;
14500
14530
  }
14501
- if (result.isError) {
14502
- throw new Error(toolMessage(result));
14503
- }
14504
- const text = toolMessage(result);
14505
- const listed = JSON.parse(text);
14531
+ const listed = result;
14506
14532
  if (!Array.isArray(listed)) {
14507
14533
  throw new Error("Connector list is unavailable.");
14508
14534
  }
@@ -14524,7 +14550,7 @@ function ConnectionSetupPage({
14524
14550
  return () => {
14525
14551
  active = false;
14526
14552
  };
14527
- }, [callServerTool, isReady, initialConnection]);
14553
+ }, [callServerTool, isReady, initialConnection, actions2]);
14528
14554
  const selected = connectors.find((connector) => connector.id === type);
14529
14555
  const properties = selected?.schema?.properties ?? {};
14530
14556
  const entries = Object.entries(properties).filter(([key, field]) => {
@@ -14549,6 +14575,11 @@ function ConnectionSetupPage({
14549
14575
  ...Object.fromEntries(entries.map(([key, field]) => [key, values[key] ?? field.default]))
14550
14576
  });
14551
14577
  const test = async () => {
14578
+ if (actions2) {
14579
+ await actions2.test(profile(), password || void 0);
14580
+ setTested(true);
14581
+ return true;
14582
+ }
14552
14583
  const result = await callServerTool(
14553
14584
  "test_connection",
14554
14585
  initialConnection && !password ? { connectionId: draftId } : { type, connectionProfile: { ...profile(), ...password ? { password } : {} } }
@@ -14573,6 +14604,12 @@ function ConnectionSetupPage({
14573
14604
  return;
14574
14605
  }
14575
14606
  const id = draftId;
14607
+ if (actions2) {
14608
+ await actions2.save(profile(), password || void 0);
14609
+ setPassword("");
14610
+ onDone(id);
14611
+ return;
14612
+ }
14576
14613
  const saved = await callServerTool("save_connection", {
14577
14614
  connectionProfile: { ...profile(), id }
14578
14615
  });
@@ -14649,8 +14686,8 @@ function ConnectionSetupPage({
14649
14686
  };
14650
14687
  return /* @__PURE__ */ jsxRuntimeExports.jsx("main", { className: "grain-connection-page", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-content", children: [
14651
14688
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-intro", children: [
14652
- /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: initialConnection ? "Edit connection" : "Connect a database" }),
14653
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Add a connection to start querying your data." })
14689
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: actions2 ? "Finish connection setup" : initialConnection ? "Edit connection" : "Connect a database" }),
14690
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: actions2 ? "Test and save this connection to continue." : "Add a connection to start querying your data." })
14654
14691
  ] }),
14655
14692
  /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleSubmit, children: [
14656
14693
  error && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-error", role: "alert", children: [
@@ -14668,7 +14705,7 @@ function ConnectionSetupPage({
14668
14705
  value: type,
14669
14706
  onChange: (event) => selectType(event.target.value),
14670
14707
  required: true,
14671
- disabled: !isReady || busy !== null,
14708
+ disabled: (actions2 ? true : !isReady) || busy !== null,
14672
14709
  children: [
14673
14710
  !type && /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "Choose a database" }),
14674
14711
  connectors.map((connector) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: connector.id, children: connector.displayName }, connector.id))
@@ -14685,20 +14722,36 @@ function ConnectionSetupPage({
14685
14722
  value: name,
14686
14723
  onChange: (event) => setName(event.target.value),
14687
14724
  placeholder: "e.g. Analytics",
14688
- required: true
14725
+ required: true,
14726
+ readOnly: Boolean(actions2)
14689
14727
  }
14690
14728
  )
14691
14729
  ] })
14692
14730
  ] }),
14731
+ displayMode === "password" && canEditDetails && /* @__PURE__ */ jsxRuntimeExports.jsx(
14732
+ "button",
14733
+ {
14734
+ type: "button",
14735
+ className: "grain-connection-secondary grain-connection-edit",
14736
+ onClick: () => setDisplayMode("full"),
14737
+ children: "Edit details"
14738
+ }
14739
+ ),
14693
14740
  selected && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
14694
- /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "grain-connection-section", children: [
14741
+ displayMode === "full" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "grain-connection-section", children: [
14695
14742
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { children: "Connection details" }),
14696
14743
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grain-connection-grid", children: entries.filter(([, field]) => !field.ui?.advanced).map(renderField) })
14744
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "grain-connection-section", children: [
14745
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { children: "Connection details" }),
14746
+ /* @__PURE__ */ jsxRuntimeExports.jsx("dl", { className: "grain-connection-readonly", children: entries.map(([key, field]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
14747
+ /* @__PURE__ */ jsxRuntimeExports.jsx("dt", { children: field.title ?? key }),
14748
+ /* @__PURE__ */ jsxRuntimeExports.jsx("dd", { children: String(values[key] ?? field.default ?? "") })
14749
+ ] }, key)) })
14697
14750
  ] }),
14698
- passwordField && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "grain-connection-section", children: [
14751
+ (passwordField || actions2 && displayMode === "password") && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "grain-connection-section", children: [
14699
14752
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { children: "Authentication" }),
14700
14753
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grain-connection-grid", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-field", children: [
14701
- /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "grain-connection-password", children: passwordField.title ?? "Password" }),
14754
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "grain-connection-password", children: passwordField?.title ?? "Password" }),
14702
14755
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-password", children: [
14703
14756
  /* @__PURE__ */ jsxRuntimeExports.jsx(
14704
14757
  "input",
@@ -14723,7 +14776,7 @@ function ConnectionSetupPage({
14723
14776
  /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Stored on this computer after connecting." })
14724
14777
  ] }) })
14725
14778
  ] }),
14726
- entries.some(([, field]) => field.ui?.advanced) && /* @__PURE__ */ jsxRuntimeExports.jsxs("details", { className: "grain-connection-advanced", children: [
14779
+ displayMode === "full" && entries.some(([, field]) => field.ui?.advanced) && /* @__PURE__ */ jsxRuntimeExports.jsxs("details", { className: "grain-connection-advanced", children: [
14727
14780
  /* @__PURE__ */ jsxRuntimeExports.jsx("summary", { children: "Advanced settings" }),
14728
14781
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grain-connection-grid", children: entries.filter(([, field]) => field.ui?.advanced).map(renderField) })
14729
14782
  ] })
@@ -75059,6 +75112,10 @@ const DENSITY_HEIGHTS = {
75059
75112
  normal: { row: 35, header: 42 },
75060
75113
  comfortable: { row: 45, header: 52 }
75061
75114
  };
75115
+ const NUMERIC_TYPE = /^(unsigned\s+|signed\s+)?(u?(tiny|small|medium|big|huge)?int(eger)?\d*|decimal|numeric|number|double|float\d*|real|long|short|byte|money)(\s+(precision|unsigned|signed))?\s*(\(\s*\d+\s*(,\s*\d+\s*)?\))?$/i;
75116
+ function isNumericColumnType(type) {
75117
+ return Boolean(type && NUMERIC_TYPE.test(type.trim()));
75118
+ }
75062
75119
  const DataGrid = reactExports.forwardRef(function DataGrid2({
75063
75120
  theme: theme2,
75064
75121
  data,
@@ -75068,6 +75125,7 @@ const DataGrid = reactExports.forwardRef(function DataGrid2({
75068
75125
  nativeTheme = false,
75069
75126
  disableFilters = false,
75070
75127
  autoSizeStrategy,
75128
+ fillWidth = false,
75071
75129
  onCellDoubleClick
75072
75130
  }, ref) {
75073
75131
  const trackUi = useTrackUi();
@@ -75236,6 +75294,21 @@ const DataGrid = reactExports.forwardRef(function DataGrid2({
75236
75294
  }),
75237
75295
  [data, getNodeValue, copyIncludesHeaders]
75238
75296
  );
75297
+ const userResizedRef = reactExports.useRef(false);
75298
+ const fillSpareWidth = reactExports.useCallback((e2) => {
75299
+ if (userResizedRef.current) return;
75300
+ const body = e2.api.getHorizontalPixelRange?.();
75301
+ const columns = (e2.api.getAllDisplayedColumns?.() ?? []).filter((c) => !c.getPinned());
75302
+ if (!body || columns.length === 0) return;
75303
+ const used = columns.reduce((sum2, c) => sum2 + c.getActualWidth(), 0);
75304
+ if (used < body.right - body.left) e2.api.sizeColumnsToFit();
75305
+ }, []);
75306
+ const noteUserResize = reactExports.useCallback((e2) => {
75307
+ if (e2.source === "uiColumnResized" && e2.finished) userResizedRef.current = true;
75308
+ }, []);
75309
+ reactExports.useEffect(() => {
75310
+ userResizedRef.current = false;
75311
+ }, [data?.columns]);
75239
75312
  const columnDefs = reactExports.useMemo(() => {
75240
75313
  if (!data?.columns) return [];
75241
75314
  const isInRange = (rowIndex, displayedColIndex) => {
@@ -75293,6 +75366,7 @@ const DataGrid = reactExports.forwardRef(function DataGrid2({
75293
75366
  return p.data[col.name];
75294
75367
  return void 0;
75295
75368
  },
75369
+ ...isNumericColumnType(col.type) ? { cellClass: "sp-num-cell", headerClass: "ag-right-aligned-header" } : {},
75296
75370
  cellClassRules: {
75297
75371
  "sp-range-cell": (p) => isInRange(p.rowIndex ?? 0, index + 1)
75298
75372
  },
@@ -75419,6 +75493,11 @@ const DataGrid = reactExports.forwardRef(function DataGrid2({
75419
75493
  onGridReady: (p) => {
75420
75494
  apiRef.current = p.api;
75421
75495
  },
75496
+ ...fillWidth ? {
75497
+ onFirstDataRendered: fillSpareWidth,
75498
+ onGridSizeChanged: fillSpareWidth,
75499
+ onColumnResized: noteUserResize
75500
+ } : {},
75422
75501
  rowData: data.rows,
75423
75502
  columnDefs,
75424
75503
  defaultColDef: {
@@ -149247,6 +149326,7 @@ function getFormInitialData(connection) {
149247
149326
  delete formData["type"];
149248
149327
  delete formData["builtIn"];
149249
149328
  delete formData["hasPassword"];
149329
+ delete formData["draft"];
149250
149330
  return formData;
149251
149331
  }
149252
149332
  function schemaHasPasswordField(schema) {
@@ -149285,9 +149365,10 @@ function schemaForConnectionMode(schema, connectorId, mode) {
149285
149365
  }
149286
149366
  };
149287
149367
  }
149368
+ const UNREACHABLE_MESSAGE = "Can't reach Grain. Start Grain and try again.";
149288
149369
  const ConnectionsManager = ({ initialAdd = false, onSubpageChange }) => {
149289
149370
  const { callServerTool, isReady, mode, sendMessage, onMessage } = useHostContext();
149290
- const grainServiceInstalled = useAppStore((state) => state.grainServiceInstalled);
149371
+ const grainServiceSetup = useAppStore((state) => state.grainServiceSetup);
149291
149372
  const supportsActiveConnection = mode === "vscode";
149292
149373
  const trackUi = useTrackUi();
149293
149374
  reactExports.useEffect(() => {
@@ -149519,7 +149600,8 @@ const ConnectionsManager = ({ initialAdd = false, onSubpageChange }) => {
149519
149600
  const profileToSave = {
149520
149601
  ...formData,
149521
149602
  type: typeToSave,
149522
- id: connection?.id ?? `conn_${Date.now()}`
149603
+ id: connection?.id ?? `conn_${Date.now()}`,
149604
+ ...connection?.draft ? { draft: false } : {}
149523
149605
  };
149524
149606
  if (isEditing2 && profileToSave["password"] === "") {
149525
149607
  delete profileToSave["password"];
@@ -149795,10 +149877,30 @@ const ConnectionsManager = ({ initialAdd = false, onSubpageChange }) => {
149795
149877
  }
149796
149878
  return null;
149797
149879
  };
149880
+ const renderUnreachableActions = () => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { marginTop: 12, display: "flex", gap: 8 }, children: [
149881
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "sp-btn", onClick: fetchData, children: "Retry" }),
149882
+ mode === "vscode" && /* @__PURE__ */ jsxRuntimeExports.jsx(
149883
+ "button",
149884
+ {
149885
+ className: "sp-btn",
149886
+ onClick: () => sendMessage?.(grainServiceSetup === "not-set-up" ? "setUpGrain" : "startGrain"),
149887
+ children: grainServiceSetup === "not-set-up" ? "Set up Grain" : "Start Grain"
149888
+ }
149889
+ )
149890
+ ] });
149891
+ const renderUnreachableState = () => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-conn-empty", children: [
149892
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: UNREACHABLE_MESSAGE }),
149893
+ renderUnreachableActions()
149894
+ ] });
149798
149895
  const renderConnectionForm = () => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-detail", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-conn-detail-body", children: [
149799
149896
  renderToast(),
149800
- !selectedType && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-conn-form-section", children: "Database Type" }),
149801
- !selectedType && ["local", "server"].map((group) => {
149897
+ !selectedType && connectors.length === 0 && connectionsUnreachable && renderUnreachableState(),
149898
+ !selectedType && connectors.length === 0 && !connectionsUnreachable && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-conn-empty", children: [
149899
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "No database types are available." }),
149900
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { marginTop: 12 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "sp-btn", onClick: fetchData, children: "Retry" }) })
149901
+ ] }),
149902
+ !selectedType && connectors.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-conn-form-section", children: "Database Type" }),
149903
+ !selectedType && connectors.length > 0 && ["local", "server"].map((group) => {
149802
149904
  const groupConnectors = connectors.filter((c) => getConnectorGroup(c.id) === group);
149803
149905
  if (groupConnectors.length === 0) {
149804
149906
  return null;
@@ -149975,11 +150077,8 @@ const ConnectionsManager = ({ initialAdd = false, onSubpageChange }) => {
149975
150077
  gap: "12px"
149976
150078
  },
149977
150079
  children: connections.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-conn-empty", style: { gridColumn: "1 / -1" }, children: [
149978
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: connectionsUnreachable ? "Can't reach Grain. Start Grain and try again." : "No connections configured." }),
149979
- connectionsUnreachable && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { marginTop: 12, display: "flex", gap: 8 }, children: [
149980
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "sp-btn", onClick: fetchData, children: "Retry" }),
149981
- mode === "vscode" && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "sp-btn", onClick: () => sendMessage?.("startGrain"), children: grainServiceInstalled ? "Start Grain" : "Set up Grain" })
149982
- ] }),
150080
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: connectionsUnreachable ? UNREACHABLE_MESSAGE : "No connections configured." }),
150081
+ connectionsUnreachable && renderUnreachableActions(),
149983
150082
  mode === "vscode" && !connectionsUnreachable && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { marginTop: 12 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
149984
150083
  "button",
149985
150084
  {
@@ -150056,7 +150155,8 @@ const ConnectionsManager = ({ initialAdd = false, onSubpageChange }) => {
150056
150155
  },
150057
150156
  children: [
150058
150157
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `sp-dot sp-dot-${status}` }),
150059
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--sp-muted-fg)" }, children: statusLabel }),
150158
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--sp-muted-fg)" }, children: c.draft ? "Needs setup" : statusLabel }),
150159
+ c.draft && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-pill", children: "Draft" }),
150060
150160
  isActive && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-pill", style: { marginLeft: "auto" }, children: "Active" })
150061
150161
  ]
150062
150162
  }
@@ -150080,7 +150180,8 @@ const ConnectionsManager = ({ initialAdd = false, onSubpageChange }) => {
150080
150180
  meta: selectedConnection && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
150081
150181
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-conn-type-badge", children: selectedConnection.type }),
150082
150182
  supportsActiveConnection && activeConnectionId === selectedConnection.id && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-conn-active-badge", children: "Active" }),
150083
- selectedConnection.builtIn && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-conn-built-in-label", children: "Built-in" })
150183
+ selectedConnection.builtIn && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-conn-built-in-label", children: "Built-in" }),
150184
+ selectedConnection.draft && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-conn-built-in-label", children: "Draft" })
150084
150185
  ] }),
150085
150186
  actions: isFormOpen ? /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { variant: "ghost", onClick: closeForm, "aria-label": "Cancel", children: "Cancel" }) : selectedConnection && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
150086
150187
  supportsActiveConnection && /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -150251,6 +150352,10 @@ const TOOL_ENTRIES = {
150251
150352
  list_connections: ["connections", "Show saved connection profiles."],
150252
150353
  get_demo_dataset: ["connections", "Offer the built-in bookstore demo data and sample queries."],
150253
150354
  save_connection: ["connections", "Create or update a connection profile."],
150355
+ create_connection_draft: [
150356
+ "connections",
150357
+ "Create a draft connection and give the user a setup link."
150358
+ ],
150254
150359
  request_connection_credentials: ["connections", "Ask the user to enter a connection password."],
150255
150360
  test_connection: ["connections", "Verify a saved or draft profile."],
150256
150361
  delete_connection: ["connections", "Remove a saved profile."],
@@ -150758,13 +150863,13 @@ function SettingsPanel({ onSave, onClose, onShowWelcome, initialTab = "general"
150758
150863
  title: "This daemon can run standalone or inside VS Code; check the releases page for the latest version.",
150759
150864
  children: "View Releases"
150760
150865
  }
150761
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
150866
+ ) : updateAvailable && /* @__PURE__ */ jsxRuntimeExports.jsx(
150762
150867
  "button",
150763
150868
  {
150764
150869
  type: "button",
150765
150870
  className: "sp-btn",
150766
150871
  onClick: () => sendMessage("openExtensionPage", {}),
150767
- children: updateAvailable ? "Open Update" : "Open Extension"
150872
+ children: "Open Update"
150768
150873
  }
150769
150874
  )
150770
150875
  ] }),
@@ -151423,7 +151528,7 @@ const LandingScreen = ({
151423
151528
  activeConnectionName: storeConnName,
151424
151529
  activeConnectorType: storeConnType,
151425
151530
  openSettingsPanel,
151426
- grainServiceInstalled
151531
+ grainServiceSetup
151427
151532
  } = useAppStore();
151428
151533
  const [fetchedConnName, setFetchedConnName] = reactExports.useState(null);
151429
151534
  const [fetchedConnType, setFetchedConnType] = reactExports.useState(null);
@@ -151498,9 +151603,13 @@ const LandingScreen = ({
151498
151603
  ] }),
151499
151604
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-landing-subtitle", children: "Execute SQL queries directly in your IDE." })
151500
151605
  ] }),
151501
- !hasActiveConnection && grainUnavailable && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-landing-active-connection", children: [
151502
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: grainServiceInstalled ? "Grain is stopped. Start Grain to see your connections." : "Grain is not set up yet. Setup installs the Grain service once." }),
151503
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "sp-btn", onClick: () => sendMessage("startGrain"), children: grainServiceInstalled ? "Start Grain" : "Set up Grain" })
151606
+ grainServiceSetup === "not-set-up" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-landing-active-connection", children: [
151607
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Grain's background service isn't set up. Set it up once so your agents and the grain CLI can reach Grain when VS Code is closed." }),
151608
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "sp-btn", onClick: () => sendMessage("setUpGrain"), children: "Set up Grain" })
151609
+ ] }),
151610
+ grainServiceSetup !== "not-set-up" && !hasActiveConnection && grainUnavailable && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-landing-active-connection", children: [
151611
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: grainServiceSetup === "registered" ? "Grain is stopped. Start Grain to see your connections." : "Grain starts when needed on this machine." }),
151612
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "sp-btn", onClick: () => sendMessage("startGrain"), children: "Start Grain" })
151504
151613
  ] }),
151505
151614
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
151506
151615
  "div",
@@ -151630,7 +151739,7 @@ function SafeModeIndicator({
151630
151739
  capability
151631
151740
  ] });
151632
151741
  }
151633
- function MultiTabLayout({ emptyState } = {}) {
151742
+ function MultiTabLayout({ emptyState, fillGridWidth = false } = {}) {
151634
151743
  const {
151635
151744
  tabs,
151636
151745
  activeTabId,
@@ -152319,6 +152428,7 @@ ${activeTab.errorDetails}` : activeTab.error;
152319
152428
  data: activeTab.result,
152320
152429
  copyIncludesHeaders,
152321
152430
  density: rowDensity,
152431
+ fillWidth: fillGridWidth,
152322
152432
  onCellDoubleClick: handleCellDoubleClick
152323
152433
  }
152324
152434
  )
@@ -152332,6 +152442,7 @@ ${activeTab.errorDetails}` : activeTab.error;
152332
152442
  data: activeTab.result,
152333
152443
  copyIncludesHeaders,
152334
152444
  density: rowDensity,
152445
+ fillWidth: fillGridWidth,
152335
152446
  onCellDoubleClick: handleCellDoubleClick
152336
152447
  }
152337
152448
  ),
@@ -152393,6 +152504,60 @@ ${activeTab.errorDetails}` : activeTab.error ?? "",
152393
152504
  )
152394
152505
  ] });
152395
152506
  }
152507
+ function SidebarAddButton({ label, title, onClick }) {
152508
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
152509
+ "button",
152510
+ {
152511
+ type: "button",
152512
+ className: "sp-web-section-add",
152513
+ onClick,
152514
+ title,
152515
+ "aria-label": label,
152516
+ children: "+"
152517
+ }
152518
+ );
152519
+ }
152520
+ function SqlEditor({ value, onChange, onRun }) {
152521
+ const frameRef = reactExports.useRef(null);
152522
+ const gutterRef = reactExports.useRef(null);
152523
+ const lineCount = value.split("\n").length;
152524
+ const digits = Math.max(2, String(lineCount).length);
152525
+ reactExports.useLayoutEffect(() => {
152526
+ frameRef.current?.style.setProperty("--sp-web-gutter-digits", String(digits));
152527
+ }, [digits]);
152528
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: frameRef, className: "sp-web-sql-editor-frame", children: [
152529
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
152530
+ "div",
152531
+ {
152532
+ ref: gutterRef,
152533
+ className: "sp-web-sql-gutter",
152534
+ "aria-hidden": "true",
152535
+ "data-testid": "sql-gutter",
152536
+ children: Array.from({ length: lineCount }, (_, index) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: index + 1 }, index))
152537
+ }
152538
+ ),
152539
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
152540
+ "textarea",
152541
+ {
152542
+ className: "sp-web-sql-editor",
152543
+ "aria-label": "SQL editor",
152544
+ spellCheck: false,
152545
+ wrap: "off",
152546
+ value,
152547
+ onChange: (event) => onChange(event.target.value),
152548
+ onScroll: (event) => {
152549
+ if (gutterRef.current) gutterRef.current.scrollTop = event.currentTarget.scrollTop;
152550
+ },
152551
+ onKeyDown: (event) => {
152552
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
152553
+ event.preventDefault();
152554
+ onRun();
152555
+ }
152556
+ }
152557
+ }
152558
+ )
152559
+ ] });
152560
+ }
152396
152561
  const THEME_KEY = "grain:web-theme";
152397
152562
  const SIDEBAR_DEFAULT_WIDTH = 220;
152398
152563
  const SIDEBAR_MIN_WIDTH = 160;
@@ -152495,14 +152660,15 @@ function WebApp({ viewHint }) {
152495
152660
  };
152496
152661
  const runQuery = async () => {
152497
152662
  if (!activeTab || !activeTab.sql.trim() || !selectedConnectionId || activeTab.isLoading) return;
152498
- const { id, sql } = activeTab;
152663
+ const { id, sql, title } = activeTab;
152499
152664
  updateTab(id, { isLoading: true, error: null, cancelled: false });
152500
152665
  try {
152501
152666
  const result = await callServerTool("run_query", {
152502
152667
  sql,
152503
152668
  session: WEB_SESSION_ID,
152504
152669
  tabId: activeTab.remoteId,
152505
- connectionId: selectedConnectionId
152670
+ connectionId: selectedConnectionId,
152671
+ title
152506
152672
  });
152507
152673
  const data = result.structuredContent;
152508
152674
  if (useAppStore.getState().tabs.find((tab) => tab.id === id)?.cancelled) return;
@@ -152869,6 +153035,14 @@ function WebApp({ viewHint }) {
152869
153035
  monitorOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("aside", { ref: sidebarRef, className: "sp-web-sidebar", "aria-label": "Workspace navigation", children: [
152870
153036
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-sidebar-header", children: [
152871
153037
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-web-sidebar-title", children: "Connections" }),
153038
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
153039
+ SidebarAddButton,
153040
+ {
153041
+ label: "Add connection",
153042
+ title: "Add connection",
153043
+ onClick: () => setSetupOpen(true)
153044
+ }
153045
+ ),
152872
153046
  /* @__PURE__ */ jsxRuntimeExports.jsx(
152873
153047
  "button",
152874
153048
  {
@@ -152877,7 +153051,7 @@ function WebApp({ viewHint }) {
152877
153051
  onClick: () => setMonitorOpen(false),
152878
153052
  title: "Hide sidebar",
152879
153053
  "aria-label": "Hide sidebar",
152880
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, { size: 14 })
153054
+ children: "«"
152881
153055
  }
152882
153056
  )
152883
153057
  ] }),
@@ -152894,36 +153068,31 @@ function WebApp({ viewHint }) {
152894
153068
  "aria-valuemax": SIDEBAR_MAX_WIDTH
152895
153069
  }
152896
153070
  ),
152897
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-connection-list", children: [
152898
- connections.length ? connections.map((connection) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
152899
- "button",
152900
- {
152901
- type: "button",
152902
- className: "sp-web-connection-item",
152903
- onClick: () => openConnection(connection.id),
152904
- children: [
152905
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-web-connection-icon", "aria-hidden": "true", children: "▤" }),
152906
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
152907
- connection.name ?? connection.id,
152908
- /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: connection.type })
152909
- ] })
152910
- ]
152911
- },
152912
- connection.id
152913
- )) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-sidebar-helper", children: "No connections yet." }),
152914
- /* @__PURE__ */ jsxRuntimeExports.jsx(
152915
- "button",
152916
- {
152917
- type: "button",
152918
- className: "sp-web-add-connection",
152919
- onClick: () => setSetupOpen(true),
152920
- children: "+ Add connection"
152921
- }
152922
- )
153071
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-connection-list", children: connections.length ? connections.map((connection) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
153072
+ "button",
153073
+ {
153074
+ type: "button",
153075
+ className: "sp-web-connection-item",
153076
+ onClick: () => openConnection(connection.id),
153077
+ children: [
153078
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-web-connection-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsx(DatabaseIcon, { size: 15 }) }),
153079
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "sp-web-connection-label", children: [
153080
+ connection.name ?? connection.id,
153081
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: connection.type })
153082
+ ] }),
153083
+ connection.engineState === "ready" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
153084
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-web-connection-dot", "aria-hidden": "true" }),
153085
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-visually-hidden", children: "Ready" })
153086
+ ] })
153087
+ ]
153088
+ },
153089
+ connection.id
153090
+ )) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-sidebar-helper", children: "No connections yet." }) }),
153091
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-sidebar-section-title", children: [
153092
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Queries" }),
153093
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SidebarAddButton, { label: "Add query", title: "New query", onClick: () => newQuery() })
152923
153094
  ] }),
152924
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-sidebar-section-title", children: "Queries" }),
152925
153095
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-session-list", children: [
152926
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "sp-web-add-connection", onClick: () => newQuery(), children: "+ New query" }),
152927
153096
  closedQueries.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "sp-web-add-connection", onClick: reopenQuery, children: "Reopen closed query" }),
152928
153097
  tabs.filter((tab) => tab.sessionId === WEB_SESSION_ID).map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsx(
152929
153098
  "button",
@@ -153141,21 +153310,18 @@ function WebApp({ viewHint }) {
153141
153310
  )
153142
153311
  ] }),
153143
153312
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-query-toolbar", children: [
153144
- /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { children: [
153145
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Connection" }),
153146
- /* @__PURE__ */ jsxRuntimeExports.jsx(
153147
- "select",
153148
- {
153149
- "aria-label": "Query connection",
153150
- value: selectedConnectionId,
153151
- onChange: (event) => setQueryConnections((current2) => ({
153152
- ...current2,
153153
- [activeTab.id]: event.target.value
153154
- })),
153155
- children: connections.map((connection) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: connection.id, children: connection.name ?? connection.id }, connection.id))
153156
- }
153157
- )
153158
- ] }),
153313
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
153314
+ "select",
153315
+ {
153316
+ "aria-label": "Query connection",
153317
+ value: selectedConnectionId,
153318
+ onChange: (event) => setQueryConnections((current2) => ({
153319
+ ...current2,
153320
+ [activeTab.id]: event.target.value
153321
+ })),
153322
+ children: connections.map((connection) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: connection.id, children: connection.name ?? connection.id }, connection.id))
153323
+ }
153324
+ ) }),
153159
153325
  activeTab.isLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(
153160
153326
  "button",
153161
153327
  {
@@ -153172,6 +153338,7 @@ function WebApp({ viewHint }) {
153172
153338
  onClick: () => void runQuery(),
153173
153339
  disabled: !selectedConnectionId || !activeTab.sql.trim(),
153174
153340
  children: [
153341
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-web-run-glyph", "aria-hidden": "true", children: "▶" }),
153175
153342
  "Run ",
153176
153343
  /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { children: "⌘↵" })
153177
153344
  ]
@@ -153179,25 +153346,16 @@ function WebApp({ viewHint }) {
153179
153346
  )
153180
153347
  ] }),
153181
153348
  /* @__PURE__ */ jsxRuntimeExports.jsx(
153182
- "textarea",
153349
+ SqlEditor,
153183
153350
  {
153184
- className: "sp-web-sql-editor",
153185
- "aria-label": "SQL editor",
153186
- spellCheck: false,
153187
153351
  value: activeTab.sql,
153188
- onChange: (event) => updateTab(activeTab.id, { sql: event.target.value }),
153189
- onKeyDown: (event) => {
153190
- if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
153191
- event.preventDefault();
153192
- void runQuery();
153193
- }
153194
- }
153352
+ onChange: (sql) => updateTab(activeTab.id, { sql }),
153353
+ onRun: () => void runQuery()
153195
153354
  }
153196
153355
  )
153197
- ] }),
153198
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-results-label", children: "Results" })
153356
+ ] })
153199
153357
  ] }),
153200
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-results", hidden: Boolean(activeConnectionId), children: /* @__PURE__ */ jsxRuntimeExports.jsx(MultiTabLayout, { emptyState: resultsEmptyState }) })
153358
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-results", hidden: Boolean(activeConnectionId), children: /* @__PURE__ */ jsxRuntimeExports.jsx(MultiTabLayout, { emptyState: resultsEmptyState, fillGridWidth: true }) })
153201
153359
  ] })
153202
153360
  ] })
153203
153361
  ] });
@@ -163150,6 +163308,12 @@ body.vscode-high-contrast .ag-theme-quartz {
163150
163308
  outline: none !important;
163151
163309
  }
163152
163310
 
163311
+ .sp-num-cell {
163312
+ justify-content: flex-end;
163313
+ text-align: right;
163314
+ font-variant-numeric: tabular-nums;
163315
+ }
163316
+
163153
163317
  .sp-null {
163154
163318
  color: var(--sp-muted-fg);
163155
163319
  font-style: italic;
@@ -166216,10 +166380,54 @@ body {
166216
166380
  letter-spacing: 0;
166217
166381
  }
166218
166382
  .sp-web-sidebar-section-title {
166219
- margin: 18px 9px 8px;
166220
- padding-top: 17px;
166383
+ display: flex;
166384
+ align-items: center;
166385
+ justify-content: space-between;
166386
+ margin: 18px 0 6px;
166387
+ padding: 17px 9px 0;
166221
166388
  border-top: 1px solid var(--grain-border-subtle);
166222
166389
  }
166390
+ .sp-web-sidebar-header .sp-web-sidebar-title {
166391
+ flex: 1;
166392
+ }
166393
+ .sp-web-section-add,
166394
+ .sp-web-sidebar-close {
166395
+ display: inline-flex;
166396
+ align-items: center;
166397
+ justify-content: center;
166398
+ width: 22px;
166399
+ height: 22px;
166400
+ padding: 0;
166401
+ border: 0;
166402
+ border-radius: var(--grain-radius-sm);
166403
+ background: transparent;
166404
+ color: var(--grain-ink-muted);
166405
+ font: 400 16px/1 var(--grain-font-body);
166406
+ cursor: pointer;
166407
+ }
166408
+ .sp-web-sidebar-close {
166409
+ font-size: 13px;
166410
+ }
166411
+ .sp-web-section-add:hover,
166412
+ .sp-web-sidebar-close:hover {
166413
+ background: var(--grain-surface-raised);
166414
+ color: var(--grain-ink);
166415
+ }
166416
+ .sp-web-section-add:focus-visible,
166417
+ .sp-web-sidebar-close:focus-visible {
166418
+ outline: 1px solid var(--grain-accent);
166419
+ }
166420
+ .sp-web-connection-label {
166421
+ flex: 1;
166422
+ min-width: 0;
166423
+ }
166424
+ .sp-web-connection-dot {
166425
+ width: 7px;
166426
+ height: 7px;
166427
+ flex: none;
166428
+ border-radius: 50%;
166429
+ background: var(--sp-success);
166430
+ }
166223
166431
  .sp-web-connection-item {
166224
166432
  display: flex;
166225
166433
  align-items: center;
@@ -166338,7 +166546,7 @@ body {
166338
166546
  .sp-web-query-tabs {
166339
166547
  display: flex;
166340
166548
  align-items: stretch;
166341
- min-height: 38px;
166549
+ min-height: 42px;
166342
166550
  overflow-x: auto;
166343
166551
  border-bottom: 1px solid var(--grain-border-subtle);
166344
166552
  background: var(--grain-surface);
@@ -166352,6 +166560,9 @@ body {
166352
166560
  background: var(--grain-ground);
166353
166561
  box-shadow: inset 0 -2px var(--grain-accent);
166354
166562
  }
166563
+ .sp-web-query-tab.active button:first-child {
166564
+ color: var(--grain-accent);
166565
+ }
166355
166566
  .sp-web-query-tab button,
166356
166567
  .sp-web-query-tab-action {
166357
166568
  border: 0;
@@ -166359,7 +166570,7 @@ body {
166359
166570
  color: var(--grain-ink);
166360
166571
  font: inherit;
166361
166572
  cursor: pointer;
166362
- padding: 8px 10px;
166573
+ padding: 8px 14px;
166363
166574
  white-space: nowrap;
166364
166575
  }
166365
166576
  .sp-web-query-tab button:last-child {
@@ -166375,7 +166586,7 @@ body {
166375
166586
  width: 100%;
166376
166587
  padding: 0;
166377
166588
  border: 0;
166378
- resize: vertical;
166589
+ resize: none;
166379
166590
  background: transparent;
166380
166591
  color: var(--grain-ink);
166381
166592
  font-family: var(--grain-font-body);
@@ -166434,26 +166645,43 @@ body {
166434
166645
  opacity: 0.5;
166435
166646
  cursor: default;
166436
166647
  }
166648
+ .sp-web-sql-editor-frame {
166649
+ --sp-web-gutter-width: calc(var(--sp-web-gutter-digits, 2) * 1ch + 28px);
166650
+ position: relative;
166651
+ border-bottom: 1px solid var(--grain-border-subtle);
166652
+ background: var(--grain-ground);
166653
+ }
166654
+ .sp-web-sql-gutter {
166655
+ position: absolute;
166656
+ top: 0;
166657
+ bottom: 0;
166658
+ left: 0;
166659
+ box-sizing: border-box;
166660
+ width: var(--sp-web-gutter-width);
166661
+ overflow: hidden;
166662
+ padding: 20px 12px 40px 0;
166663
+ border-right: 1px solid var(--grain-border-subtle);
166664
+ color: var(--grain-ink-muted);
166665
+ font: 12px/1.7 var(--grain-font-mono);
166666
+ text-align: right;
166667
+ user-select: none;
166668
+ }
166437
166669
  .sp-web-sql-editor {
166438
166670
  display: block;
166439
166671
  box-sizing: border-box;
166440
- width: 100%;
166672
+ width: calc(100% - var(--sp-web-gutter-width));
166441
166673
  min-height: 190px;
166442
- padding: 20px 24px;
166674
+ margin-left: var(--sp-web-gutter-width);
166675
+ padding: 20px 24px 20px 16px;
166443
166676
  border: 0;
166444
- border-bottom: 1px solid var(--grain-border-subtle);
166445
166677
  resize: vertical;
166678
+ white-space: pre;
166446
166679
  outline-color: var(--grain-accent);
166447
166680
  background: var(--grain-ground);
166448
166681
  color: var(--grain-ink);
166449
166682
  font: 12px/1.7 var(--grain-font-mono);
166450
166683
  tab-size: 2;
166451
166684
  }
166452
- .sp-web-results-label {
166453
- padding: 10px 24px;
166454
- font-size: 12px;
166455
- font-weight: 700;
166456
- }
166457
166685
  .sp-web-results {
166458
166686
  display: flex;
166459
166687
  flex: 1;
@@ -166480,6 +166708,15 @@ body {
166480
166708
  font-weight: 700;
166481
166709
  }
166482
166710
 
166711
+ .sp-web-run-glyph {
166712
+ margin-right: 6px;
166713
+ font-size: 9px;
166714
+ }
166715
+ .sp-web-query-toolbar > .sp-web-primary-button {
166716
+ display: inline-flex;
166717
+ align-items: center;
166718
+ font-weight: 600;
166719
+ }
166483
166720
  .sp-web-query-toolbar kbd {
166484
166721
  margin-left: 6px;
166485
166722
  padding: 2px 5px;
@@ -166545,6 +166782,8 @@ body {
166545
166782
  .sp-web-connection-item:hover,
166546
166783
  .sp-web-connection-item:focus-visible {
166547
166784
  background: var(--grain-surface);
166785
+ }
166786
+ .sp-web-connection-item:focus-visible {
166548
166787
  outline: 1px solid var(--grain-accent);
166549
166788
  }
166550
166789
  .sp-web-connection-detail {