@rebasepro/studio 0.23.0 → 0.24.0

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.
Files changed (57) hide show
  1. package/dist/{ApiExplorer-pYMZflL3.js → ApiExplorer-CCtczj3F.js} +15 -28
  2. package/dist/ApiExplorer-CCtczj3F.js.map +1 -0
  3. package/dist/ApiKeysView-B66TdzhH.js +1738 -0
  4. package/dist/ApiKeysView-B66TdzhH.js.map +1 -0
  5. package/dist/BackupsView-DmSMzAbT.js +368 -0
  6. package/dist/BackupsView-DmSMzAbT.js.map +1 -0
  7. package/dist/{BranchesView-D6Sz1a5f.js → BranchesView-DP1a87E-.js} +12 -8
  8. package/dist/BranchesView-DP1a87E-.js.map +1 -0
  9. package/dist/{CronJobsView-7hjgSYId.js → CronJobsView-TDErHTuK.js} +126 -60
  10. package/dist/CronJobsView-TDErHTuK.js.map +1 -0
  11. package/dist/{JSEditor-qiuwmcvk.js → JSEditor-B59pwg4c.js} +120 -74
  12. package/dist/JSEditor-B59pwg4c.js.map +1 -0
  13. package/dist/{LogsExplorer-Xq8K4oo6.js → LogsExplorer-BKM4yfP8.js} +48 -22
  14. package/dist/LogsExplorer-BKM4yfP8.js.map +1 -0
  15. package/dist/{RLSEditor-Djh43lBE.js → RLSEditor-BlLLV6xs.js} +237 -77
  16. package/dist/RLSEditor-BlLLV6xs.js.map +1 -0
  17. package/dist/{SQLEditor-TBw_K6CW.js → SQLEditor-1An7w0j6.js} +369 -294
  18. package/dist/SQLEditor-1An7w0j6.js.map +1 -0
  19. package/dist/{SchemaVisualizer-DtDzIUNy.js → SchemaVisualizer-DCLVfA5t.js} +20 -24
  20. package/dist/SchemaVisualizer-DCLVfA5t.js.map +1 -0
  21. package/dist/{StorageView-CMG1DOVX.js → StorageView-CbsHuCBf.js} +355 -225
  22. package/dist/StorageView-CbsHuCBf.js.map +1 -0
  23. package/dist/components/ApiExplorer/TryItPanel.d.ts +6 -1
  24. package/dist/components/ApiKeys/CreateApiKeyDialog.d.ts +21 -2
  25. package/dist/components/ApiKeys/scope-words.d.ts +37 -0
  26. package/dist/components/ApiKeys/scopes.d.ts +113 -0
  27. package/dist/components/AuthSimulationSelector.d.ts +5 -1
  28. package/dist/components/LogsExplorer/useLogTail.d.ts +9 -0
  29. package/dist/components/RLSEditor/policyListing.d.ts +44 -0
  30. package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +0 -1
  31. package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +6 -0
  32. package/dist/components/load-failure.d.ts +1 -1
  33. package/dist/components/useRunAsUsers.d.ts +25 -0
  34. package/dist/index.es.js +11 -11
  35. package/dist/{load-failure-C6OMJSGC.js → load-failure-Bd8lFIO7.js} +2 -2
  36. package/dist/load-failure-Bd8lFIO7.js.map +1 -0
  37. package/dist/useRunAsUsers-CLaAtXt6.js +122 -0
  38. package/dist/useRunAsUsers-CLaAtXt6.js.map +1 -0
  39. package/dist/utils/sql_utils.d.ts +90 -49
  40. package/package.json +9 -9
  41. package/dist/ApiExplorer-pYMZflL3.js.map +0 -1
  42. package/dist/ApiKeysView-qLSs47tw.js +0 -1239
  43. package/dist/ApiKeysView-qLSs47tw.js.map +0 -1
  44. package/dist/AuthSimulationSelector-BewmhMBw.js +0 -56
  45. package/dist/AuthSimulationSelector-BewmhMBw.js.map +0 -1
  46. package/dist/BackupsView-DYTAD6aW.js +0 -253
  47. package/dist/BackupsView-DYTAD6aW.js.map +0 -1
  48. package/dist/BranchesView-D6Sz1a5f.js.map +0 -1
  49. package/dist/CronJobsView-7hjgSYId.js.map +0 -1
  50. package/dist/JSEditor-qiuwmcvk.js.map +0 -1
  51. package/dist/LogsExplorer-Xq8K4oo6.js.map +0 -1
  52. package/dist/RLSEditor-Djh43lBE.js.map +0 -1
  53. package/dist/SQLEditor-TBw_K6CW.js.map +0 -1
  54. package/dist/SchemaVisualizer-DtDzIUNy.js.map +0 -1
  55. package/dist/StorageView-CMG1DOVX.js.map +0 -1
  56. package/dist/components/ApiKeys/permissions.d.ts +0 -66
  57. package/dist/load-failure-C6OMJSGC.js.map +0 -1
@@ -0,0 +1,25 @@
1
+ import { type SelectableUser } from "@rebasepro/app";
2
+ export interface RunAsUsers {
3
+ /** The signed-in user first, then the users the latest search found. */
4
+ users: SelectableUser[];
5
+ /** The signed-in user, the picker's "self" row. */
6
+ currentUser: SelectableUser | null;
7
+ loading: boolean;
8
+ /** Search the project's users on the server. */
9
+ onSearchTextChange: (searchText: string) => void;
10
+ /**
11
+ * How many users the latest search matched on the server, set only when
12
+ * that is more than the one page listed — so the picker can say so rather
13
+ * than present the page as everyone.
14
+ */
15
+ totalCount?: number;
16
+ }
17
+ /**
18
+ * The users a "Run as" picker offers.
19
+ *
20
+ * The signed-in user, and — for an administrator, the only caller the server
21
+ * lets run a request as someone else — the project's users, searched on the
22
+ * server. Disabled accounts are left out: nothing can run as them, and the
23
+ * server refuses them.
24
+ */
25
+ export declare function useRunAsUsers(): RunAsUsers;
package/dist/index.es.js CHANGED
@@ -550,17 +550,17 @@ function SyntaxHighlightedSnippet() {
550
550
  }
551
551
  //#endregion
552
552
  //#region src/components/RebaseStudio.tsx
553
- var SQLEditor = lazyChunk(() => import("./SQLEditor-TBw_K6CW.js").then((m) => ({ default: m.SQLEditor })));
554
- var JSEditor = lazyChunk(() => import("./JSEditor-qiuwmcvk.js").then((m) => ({ default: m.JSEditor })));
555
- var RLSEditor = lazyChunk(() => import("./RLSEditor-Djh43lBE.js").then((m) => ({ default: m.RLSEditor })));
556
- var StorageView = lazyChunk(() => import("./StorageView-CMG1DOVX.js").then((m) => ({ default: m.StorageView })));
557
- var CronJobsView = lazyChunk(() => import("./CronJobsView-7hjgSYId.js").then((m) => ({ default: m.CronJobsView })));
558
- var SchemaVisualizer = lazyChunk(() => import("./SchemaVisualizer-DtDzIUNy.js").then((m) => ({ default: m.SchemaVisualizer })));
559
- var BranchesView = lazyChunk(() => import("./BranchesView-D6Sz1a5f.js").then((m) => ({ default: m.BranchesView })));
560
- var BackupsView = lazyChunk(() => import("./BackupsView-DYTAD6aW.js").then((m) => ({ default: m.BackupsView })));
561
- var ApiExplorer = lazyChunk(() => import("./ApiExplorer-pYMZflL3.js").then((m) => ({ default: m.ApiExplorer })));
562
- var LogsExplorer = lazyChunk(() => import("./LogsExplorer-Xq8K4oo6.js").then((m) => ({ default: m.LogsExplorer })));
563
- var ApiKeysView = lazyChunk(() => import("./ApiKeysView-qLSs47tw.js").then((m) => ({ default: m.ApiKeysView })));
553
+ var SQLEditor = lazyChunk(() => import("./SQLEditor-1An7w0j6.js").then((m) => ({ default: m.SQLEditor })));
554
+ var JSEditor = lazyChunk(() => import("./JSEditor-B59pwg4c.js").then((m) => ({ default: m.JSEditor })));
555
+ var RLSEditor = lazyChunk(() => import("./RLSEditor-BlLLV6xs.js").then((m) => ({ default: m.RLSEditor })));
556
+ var StorageView = lazyChunk(() => import("./StorageView-CbsHuCBf.js").then((m) => ({ default: m.StorageView })));
557
+ var CronJobsView = lazyChunk(() => import("./CronJobsView-TDErHTuK.js").then((m) => ({ default: m.CronJobsView })));
558
+ var SchemaVisualizer = lazyChunk(() => import("./SchemaVisualizer-DCLVfA5t.js").then((m) => ({ default: m.SchemaVisualizer })));
559
+ var BranchesView = lazyChunk(() => import("./BranchesView-DP1a87E-.js").then((m) => ({ default: m.BranchesView })));
560
+ var BackupsView = lazyChunk(() => import("./BackupsView-DmSMzAbT.js").then((m) => ({ default: m.BackupsView })));
561
+ var ApiExplorer = lazyChunk(() => import("./ApiExplorer-CCtczj3F.js").then((m) => ({ default: m.ApiExplorer })));
562
+ var LogsExplorer = lazyChunk(() => import("./LogsExplorer-BKM4yfP8.js").then((m) => ({ default: m.LogsExplorer })));
563
+ var ApiKeysView = lazyChunk(() => import("./ApiKeysView-B66TdzhH.js").then((m) => ({ default: m.ApiKeysView })));
564
564
  /**
565
565
  * Declarative component to configure the Studio in Rebase.
566
566
  * Renders nothing — purely registers config into the RebaseRegistry.
@@ -15,7 +15,7 @@ function statusOf(err) {
15
15
  function classifyLoadFailure(err) {
16
16
  const detail = err instanceof Error ? err.message : String(err);
17
17
  const status = statusOf(err);
18
- return status === 403 || status === 401 || /\bnot authori[sz]ed\b|\bforbidden\b|\bpermission denied\b/i.test(detail) ? {
18
+ return (status !== null ? status === 403 : /\bnot authori[sz]ed\b|\bforbidden\b|\bpermission denied\b/i.test(detail)) ? {
19
19
  kind: "denied",
20
20
  detail,
21
21
  retryable: false
@@ -28,4 +28,4 @@ function classifyLoadFailure(err) {
28
28
  //#endregion
29
29
  export { classifyLoadFailure as t };
30
30
 
31
- //# sourceMappingURL=load-failure-C6OMJSGC.js.map
31
+ //# sourceMappingURL=load-failure-Bd8lFIO7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-failure-Bd8lFIO7.js","names":[],"sources":["../src/components/load-failure.ts"],"sourcesContent":["/**\n * Why a Studio listing failed — and whether it is the platform's fault.\n *\n * ## Why this is a function\n *\n * The storage pane rendered one view for every failure: a red card titled\n * **\"Error loading storage\"** with whatever string came back underneath.\n *\n * On 2026-08-27 a customer opened the Files tab of a healthy project and read\n * \"Error loading storage — Not authorized for this object\". Nothing was broken.\n * That project's own `storageAuthorize` hook refuses an unscoped listing on\n * purpose — its comment says a `list` that is not scoped to a team \"is precisely\n * the enumeration this exists to stop\" — and the Files tab always opens at the\n * bucket root. The console took a policy working exactly as written and\n * presented it as a fault in the platform, which sends the reader hunting for a\n * problem that does not exist.\n *\n * A refusal and a failure need different words, a different colour, and a\n * different next step: one is \"your rule said no\", the other is \"we could not\n * ask\". Retry is an affordance for the second and noise on the first.\n *\n * ## Why it is shared\n *\n * Storage was the only pane that had learned this. Backups, Cron Jobs, API Keys\n * and Branches each caught the error, opened a snackbar that is gone in four\n * seconds, and left their list empty — so a reader who looked away, or arrived\n * after the toast, was told \"No backups found yet\" about a database that has\n * backups and an account that may not list them. One classifier and one view, so\n * the next pane inherits the distinction instead of rediscovering it.\n *\n * The view that renders one of these is `load-failure-view.tsx`. It is a\n * separate file so that this classifier — the part with the rules in it — can\n * be tested without dragging in `@rebasepro/app` and, behind it, react-router.\n */\n\nexport type LoadFailureKind =\n /** The project's own rules, or the caller's own role, said no. Not an error. */\n | \"denied\"\n /** We could not reach or read the source — including a session the server no longer accepts. */\n | \"unavailable\";\n\nexport interface LoadFailure {\n kind: LoadFailureKind;\n /** The message as the server gave it, never re-worded. */\n detail: string;\n /** Offering retry against a standing policy teaches people the button does nothing. */\n retryable: boolean;\n}\n\n/** HTTP status carried by whatever the SDK threw, if it carries one. */\nfunction statusOf(err: unknown): number | null {\n if (typeof err !== \"object\" || err === null) return null;\n for (const key of [\"status\", \"statusCode\", \"code\"]) {\n const v = (err as Record<string, unknown>)[key];\n if (typeof v === \"number\" && v >= 100 && v < 600) return v;\n }\n return null;\n}\n\nexport function classifyLoadFailure(err: unknown): LoadFailure {\n const detail = err instanceof Error ? err.message : String(err);\n const status = statusOf(err);\n\n // 403 is the rule's answer, and the only status that is. A 401 is the\n // session — expired, revoked, signed out elsewhere — and says nothing about\n // what the caller's role may do; a 5xx is the platform, even when its\n // message says \"permission denied\" (a missing GRANT is answered that way).\n // Both are failures to read, with Retry.\n //\n // The message match is only for an error that carries no status at all: an\n // SDK that flattens an error to a string is how a refusal reached the\n // screen unclassified.\n const denied = status !== null\n ? status === 403\n : /\\bnot authori[sz]ed\\b|\\bforbidden\\b|\\bpermission denied\\b/i.test(detail);\n\n return denied\n ? { kind: \"denied\", detail, retryable: false }\n : { kind: \"unavailable\", detail, retryable: true };\n}\n"],"mappings":";;AAkDA,SAAS,SAAS,KAA6B;CAC3C,IAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM,OAAO;CACpD,KAAK,MAAM,OAAO;EAAC;EAAU;EAAc;CAAM,GAAG;EAChD,MAAM,IAAK,IAAgC;EAC3C,IAAI,OAAO,MAAM,YAAY,KAAK,OAAO,IAAI,KAAK,OAAO;CAC7D;CACA,OAAO;AACX;AAEA,SAAgB,oBAAoB,KAA2B;CAC3D,MAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;CAC9D,MAAM,SAAS,SAAS,GAAG;CAe3B,QAJe,WAAW,OACpB,WAAW,MACX,6DAA6D,KAAK,MAAM,KAGxE;EAAE,MAAM;EAAU;EAAQ,WAAW;CAAM,IAC3C;EAAE,MAAM;EAAe;EAAQ,WAAW;CAAK;AACzD"}
@@ -0,0 +1,122 @@
1
+ import { UserSelectPopover, useRebaseClient, useRebaseContext } from "@rebasepro/app";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { Button, KeyRoundIcon, Typography, iconSize } from "@rebasepro/ui";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { hasAdminRole } from "@rebasepro/types";
6
+ //#region src/components/AuthSimulationSelector.tsx
7
+ function AuthSimulationSelector({ authMode, setAuthMode, selectedUser, setSelectedUser, users, loading, onUserSearchTextChange, userTotalCount, currentUser }) {
8
+ return /* @__PURE__ */ jsxs("div", {
9
+ className: "flex flex-wrap items-center gap-3",
10
+ children: [
11
+ /* @__PURE__ */ jsx(KeyRoundIcon, {
12
+ size: iconSize.small,
13
+ className: "text-text-secondary dark:text-text-secondary-dark"
14
+ }),
15
+ /* @__PURE__ */ jsx(Typography, {
16
+ variant: "caption",
17
+ className: "text-text-secondary dark:text-text-secondary-dark text-xs font-medium",
18
+ children: "Auth:"
19
+ }),
20
+ /* @__PURE__ */ jsx(Button, {
21
+ size: "small",
22
+ variant: authMode === "jwt" ? "outlined" : "outlined",
23
+ color: authMode === "jwt" ? "primary" : "neutral",
24
+ onClick: () => setAuthMode("jwt"),
25
+ className: "rounded-full !px-3 !py-1 !text-xs",
26
+ children: "JWT Token"
27
+ }),
28
+ /* @__PURE__ */ jsx(Button, {
29
+ size: "small",
30
+ variant: authMode === "none" ? "outlined" : "outlined",
31
+ color: authMode === "none" ? "error" : "neutral",
32
+ onClick: () => setAuthMode("none"),
33
+ className: "rounded-full !px-3 !py-1 !text-xs",
34
+ children: "No Auth"
35
+ }),
36
+ authMode === "jwt" && /* @__PURE__ */ jsxs(Fragment, { children: [
37
+ /* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-hairline-strong mx-1" }),
38
+ /* @__PURE__ */ jsx(Typography, {
39
+ variant: "caption",
40
+ className: "text-text-secondary dark:text-text-secondary-dark text-xs font-medium",
41
+ children: "Run as:"
42
+ }),
43
+ /* @__PURE__ */ jsx(UserSelectPopover, {
44
+ selectedUser,
45
+ onUserSelected: setSelectedUser,
46
+ users,
47
+ loading,
48
+ onSearchTextChange: onUserSearchTextChange,
49
+ totalCount: userTotalCount,
50
+ currentUser
51
+ })
52
+ ] })
53
+ ]
54
+ });
55
+ }
56
+ //#endregion
57
+ //#region src/components/useRunAsUsers.ts
58
+ /** How many users one search of a "Run as" picker lists. */
59
+ var USER_PAGE_SIZE = 50;
60
+ /** The fields a picker row shows, copied off an account or the signed-in user. */
61
+ function toSelectableUser(user) {
62
+ return {
63
+ uid: user.uid,
64
+ displayName: user.displayName,
65
+ email: user.email,
66
+ photoURL: user.photoURL,
67
+ roles: user.roles
68
+ };
69
+ }
70
+ /**
71
+ * The users a "Run as" picker offers.
72
+ *
73
+ * The signed-in user, and — for an administrator, the only caller the server
74
+ * lets run a request as someone else — the project's users, searched on the
75
+ * server. Disabled accounts are left out: nothing can run as them, and the
76
+ * server refuses them.
77
+ */
78
+ function useRunAsUsers() {
79
+ const signedIn = useRebaseContext().authController?.user;
80
+ const client = useRebaseClient();
81
+ const adminApi = hasAdminRole(signedIn?.roles) ? client?.admin : void 0;
82
+ const [search, setSearch] = useState("");
83
+ const [listed, setListed] = useState([]);
84
+ const [totalCount, setTotalCount] = useState(void 0);
85
+ const [loading, setLoading] = useState(false);
86
+ useEffect(() => {
87
+ if (!adminApi) return;
88
+ let cancelled = false;
89
+ setLoading(true);
90
+ adminApi.listUsersPaginated({
91
+ search: search || void 0,
92
+ limit: USER_PAGE_SIZE
93
+ }).then(({ users, total }) => {
94
+ if (cancelled) return;
95
+ setListed(users.filter((u) => !u.disabled).map(toSelectableUser));
96
+ setTotalCount(total > users.length ? total : void 0);
97
+ }).catch((err) => {
98
+ console.warn("Could not list users for the \"Run as\" picker", err);
99
+ if (!cancelled) {
100
+ setListed([]);
101
+ setTotalCount(void 0);
102
+ }
103
+ }).finally(() => {
104
+ if (!cancelled) setLoading(false);
105
+ });
106
+ return () => {
107
+ cancelled = true;
108
+ };
109
+ }, [adminApi, search]);
110
+ const currentUser = useMemo(() => signedIn ? toSelectableUser(signedIn) : null, [signedIn]);
111
+ return {
112
+ users: useMemo(() => [...currentUser ? [currentUser] : [], ...listed.filter((u) => u.uid !== currentUser?.uid)], [currentUser, listed]),
113
+ currentUser,
114
+ loading,
115
+ onSearchTextChange: setSearch,
116
+ totalCount
117
+ };
118
+ }
119
+ //#endregion
120
+ export { AuthSimulationSelector as n, useRunAsUsers as t };
121
+
122
+ //# sourceMappingURL=useRunAsUsers-CLaAtXt6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRunAsUsers-CLaAtXt6.js","names":[],"sources":["../src/components/AuthSimulationSelector.tsx","../src/components/useRunAsUsers.ts"],"sourcesContent":["import React from \"react\";\nimport { Button, cls, iconSize, KeyRoundIcon, Typography } from \"@rebasepro/ui\";\nimport { UserSelectPopover, SelectableUser } from \"@rebasepro/app\";\n\nexport interface AuthSimulationSelectorProps {\n authMode: \"jwt\" | \"none\";\n setAuthMode: (mode: \"jwt\" | \"none\") => void;\n selectedUser: SelectableUser | null;\n setSelectedUser: (user: SelectableUser | null) => void;\n users: SelectableUser[];\n loading?: boolean;\n /** For a caller that searches `users` on the server — see `UserSelectPopover`. */\n onUserSearchTextChange?: (searchText: string) => void;\n /** How many users that search matched, when `users` is only its first page — see `UserSelectPopover`. */\n userTotalCount?: number;\n currentUser: SelectableUser | null;\n}\n\nexport function AuthSimulationSelector({\n authMode,\n setAuthMode,\n selectedUser,\n setSelectedUser,\n users,\n loading,\n onUserSearchTextChange,\n userTotalCount,\n currentUser\n}: AuthSimulationSelectorProps) {\n return (\n <div className=\"flex flex-wrap items-center gap-3\">\n <KeyRoundIcon size={iconSize.small} className=\"text-text-secondary dark:text-text-secondary-dark\" />\n <Typography\n variant=\"caption\"\n className=\"text-text-secondary dark:text-text-secondary-dark text-xs font-medium\"\n >\n Auth:\n </Typography>\n <Button\n size=\"small\"\n variant={authMode === \"jwt\" ? \"outlined\" : \"outlined\"}\n color={authMode === \"jwt\" ? \"primary\" : \"neutral\"}\n onClick={() => setAuthMode(\"jwt\")}\n className=\"rounded-full !px-3 !py-1 !text-xs\"\n >\n JWT Token\n </Button>\n <Button\n size=\"small\"\n variant={authMode === \"none\" ? \"outlined\" : \"outlined\"}\n color={authMode === \"none\" ? \"error\" : \"neutral\"}\n onClick={() => setAuthMode(\"none\")}\n className=\"rounded-full !px-3 !py-1 !text-xs\"\n >\n No Auth\n </Button>\n\n {authMode === \"jwt\" && (\n <>\n <div className=\"w-px h-4 bg-hairline-strong mx-1\" />\n <Typography\n variant=\"caption\"\n className=\"text-text-secondary dark:text-text-secondary-dark text-xs font-medium\"\n >\n Run as:\n </Typography>\n <UserSelectPopover\n selectedUser={selectedUser}\n onUserSelected={setSelectedUser}\n users={users}\n loading={loading}\n onSearchTextChange={onUserSearchTextChange}\n totalCount={userTotalCount}\n currentUser={currentUser}\n />\n </>\n )}\n </div>\n );\n}\n","import { useEffect, useMemo, useState } from \"react\";\nimport { useRebaseClient, useRebaseContext, type SelectableUser } from \"@rebasepro/app\";\nimport { hasAdminRole, type RebaseClient } from \"@rebasepro/types\";\n\n/** How many users one search of a \"Run as\" picker lists. */\nconst USER_PAGE_SIZE = 50;\n\n/** The fields a picker row shows, copied off an account or the signed-in user. */\nfunction toSelectableUser(user: SelectableUser): SelectableUser {\n return {\n uid: user.uid,\n displayName: user.displayName,\n email: user.email,\n photoURL: user.photoURL,\n roles: user.roles\n };\n}\n\nexport interface RunAsUsers {\n /** The signed-in user first, then the users the latest search found. */\n users: SelectableUser[];\n /** The signed-in user, the picker's \"self\" row. */\n currentUser: SelectableUser | null;\n loading: boolean;\n /** Search the project's users on the server. */\n onSearchTextChange: (searchText: string) => void;\n /**\n * How many users the latest search matched on the server, set only when\n * that is more than the one page listed — so the picker can say so rather\n * than present the page as everyone.\n */\n totalCount?: number;\n}\n\n/**\n * The users a \"Run as\" picker offers.\n *\n * The signed-in user, and — for an administrator, the only caller the server\n * lets run a request as someone else — the project's users, searched on the\n * server. Disabled accounts are left out: nothing can run as them, and the\n * server refuses them.\n */\nexport function useRunAsUsers(): RunAsUsers {\n const signedIn = useRebaseContext().authController?.user;\n const client = useRebaseClient<RebaseClient>();\n const adminApi = hasAdminRole(signedIn?.roles) ? client?.admin : undefined;\n\n const [search, setSearch] = useState(\"\");\n const [listed, setListed] = useState<SelectableUser[]>([]);\n const [totalCount, setTotalCount] = useState<number | undefined>(undefined);\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n if (!adminApi) return;\n let cancelled = false;\n setLoading(true);\n adminApi.listUsersPaginated({ search: search || undefined, limit: USER_PAGE_SIZE })\n .then(({ users, total }) => {\n if (cancelled) return;\n setListed(users.filter((u) => !u.disabled).map(toSelectableUser));\n setTotalCount(total > users.length ? total : undefined);\n })\n .catch((err: unknown) => {\n console.warn(\"Could not list users for the \\\"Run as\\\" picker\", err);\n if (!cancelled) {\n setListed([]);\n setTotalCount(undefined);\n }\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n return () => {\n cancelled = true;\n };\n }, [adminApi, search]);\n\n const currentUser = useMemo((): SelectableUser | null => (signedIn ? toSelectableUser(signedIn) : null), [signedIn]);\n\n const users = useMemo(\n () => [...(currentUser ? [currentUser] : []), ...listed.filter((u) => u.uid !== currentUser?.uid)],\n [currentUser, listed]\n );\n\n return { users, currentUser, loading, onSearchTextChange: setSearch, totalCount };\n}\n"],"mappings":";;;;;;AAkBA,SAAgB,uBAAuB,EACnC,UACA,aACA,cACA,iBACA,OACA,SACA,wBACA,gBACA,eAC4B;CAC5B,OACI,qBAAC,OAAD;EAAK,WAAU;YAAf;GACI,oBAAC,cAAD;IAAc,MAAM,SAAS;IAAO,WAAU;GAAqD,CAAA;GACnG,oBAAC,YAAD;IACI,SAAQ;IACR,WAAU;cACb;GAEW,CAAA;GACZ,oBAAC,QAAD;IACI,MAAK;IACL,SAAS,aAAa,QAAQ,aAAa;IAC3C,OAAO,aAAa,QAAQ,YAAY;IACxC,eAAe,YAAY,KAAK;IAChC,WAAU;cACb;GAEO,CAAA;GACR,oBAAC,QAAD;IACI,MAAK;IACL,SAAS,aAAa,SAAS,aAAa;IAC5C,OAAO,aAAa,SAAS,UAAU;IACvC,eAAe,YAAY,MAAM;IACjC,WAAU;cACb;GAEO,CAAA;GAEP,aAAa,SACV,qBAAA,UAAA,EAAA,UAAA;IACI,oBAAC,OAAD,EAAK,WAAU,mCAAoC,CAAA;IACnD,oBAAC,YAAD;KACI,SAAQ;KACR,WAAU;eACb;IAEW,CAAA;IACZ,oBAAC,mBAAD;KACkB;KACd,gBAAgB;KACT;KACE;KACT,oBAAoB;KACpB,YAAY;KACC;IAChB,CAAA;GACH,EAAA,CAAA;EAEL;;AAEb;;;;AC1EA,IAAM,iBAAiB;;AAGvB,SAAS,iBAAiB,MAAsC;CAC5D,OAAO;EACH,KAAK,KAAK;EACV,aAAa,KAAK;EAClB,OAAO,KAAK;EACZ,UAAU,KAAK;EACf,OAAO,KAAK;CAChB;AACJ;;;;;;;;;AA0BA,SAAgB,gBAA4B;CACxC,MAAM,WAAW,iBAAiB,CAAC,CAAC,gBAAgB;CACpD,MAAM,SAAS,gBAA8B;CAC7C,MAAM,WAAW,aAAa,UAAU,KAAK,IAAI,QAAQ,QAAQ,KAAA;CAEjE,MAAM,CAAC,QAAQ,aAAa,SAAS,EAAE;CACvC,MAAM,CAAC,QAAQ,aAAa,SAA2B,CAAC,CAAC;CACzD,MAAM,CAAC,YAAY,iBAAiB,SAA6B,KAAA,CAAS;CAC1E,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAE5C,gBAAgB;EACZ,IAAI,CAAC,UAAU;EACf,IAAI,YAAY;EAChB,WAAW,IAAI;EACf,SAAS,mBAAmB;GAAE,QAAQ,UAAU,KAAA;GAAW,OAAO;EAAe,CAAC,CAAC,CAC9E,MAAM,EAAE,OAAO,YAAY;GACxB,IAAI,WAAW;GACf,UAAU,MAAM,QAAQ,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,gBAAgB,CAAC;GAChE,cAAc,QAAQ,MAAM,SAAS,QAAQ,KAAA,CAAS;EAC1D,CAAC,CAAC,CACD,OAAO,QAAiB;GACrB,QAAQ,KAAK,kDAAkD,GAAG;GAClE,IAAI,CAAC,WAAW;IACZ,UAAU,CAAC,CAAC;IACZ,cAAc,KAAA,CAAS;GAC3B;EACJ,CAAC,CAAC,CACD,cAAc;GACX,IAAI,CAAC,WAAW,WAAW,KAAK;EACpC,CAAC;EACL,aAAa;GACT,YAAY;EAChB;CACJ,GAAG,CAAC,UAAU,MAAM,CAAC;CAErB,MAAM,cAAc,cAAsC,WAAW,iBAAiB,QAAQ,IAAI,MAAO,CAAC,QAAQ,CAAC;CAOnH,OAAO;EAAE,OALK,cACJ,CAAC,GAAI,cAAc,CAAC,WAAW,IAAI,CAAC,GAAI,GAAG,OAAO,QAAQ,MAAM,EAAE,QAAQ,aAAa,GAAG,CAAC,GACjG,CAAC,aAAa,MAAM,CAGf;EAAO;EAAa;EAAS,oBAAoB;EAAW;CAAW;AACpF"}
@@ -1,4 +1,5 @@
1
- import type { TableInfo } from "../components/SQLEditor/sql_editor_types.js";
1
+ import type { AdminCollection } from "@rebasepro/cms-types";
2
+ import type { SqlScriptResult } from "@rebasepro/types";
2
3
  /**
3
4
  * The `EXPLAIN` for one statement, or `null` when `sqlText` is not exactly one.
4
5
  *
@@ -34,68 +35,108 @@ export declare function acceptsAutoLimit(sqlText: string): boolean;
34
35
  */
35
36
  export declare function needsDestructiveConfirmation(sqlText: string): boolean;
36
37
  /**
37
- * A table extracted from a SQL query's FROM/JOIN clauses.
38
+ * A table as SQL: `"schema"."table"` when a schema is given, a bare `"table"`
39
+ * otherwise.
38
40
  */
39
- export interface ExtractedTable {
40
- name: string;
41
- /** The schema the query named, if it named one. */
42
- schema?: string;
43
- alias?: string;
41
+ export declare function quoteTableName(tableName: string, schemaName?: string): string;
42
+ /** An identifier as SQL, whatever it holds: double-quoted, with `"` doubled. */
43
+ export declare function quoteIdentifier(identifier: string): string;
44
+ /** What the console knows about a result: its columns, and the tables they came from. */
45
+ export type ResultProvenance = Pick<SqlScriptResult, "columns" | "tables">;
46
+ /**
47
+ * The stored cell a result cell is, and the key that finds its row.
48
+ */
49
+ export interface CellEditTarget {
50
+ schema: string;
51
+ table: string;
52
+ /** The table column the edited value is written to. */
53
+ column: string;
54
+ /** Each primary key column of the table, and the result column holding its value for the row. */
55
+ key: {
56
+ column: string;
57
+ resultColumn: string;
58
+ }[];
44
59
  }
45
60
  /**
46
- * A table as SQL: `"schema"."table"` when the query named its schema, and a
47
- * bare `"table"` when it left it to the search path — which then resolves it
48
- * the same way it resolved the query.
61
+ * Why a cell cannot be edited: a translation key of the console's, and its
62
+ * parameters.
49
63
  */
50
- export declare function quoteTableName(tableName: string, schemaName?: string): string;
64
+ export interface CellEditRefusal {
65
+ key: "studio_sql_edit_unreadable_query" | "studio_sql_edit_not_a_select" | "studio_sql_edit_duplicate_column" | "studio_sql_edit_computed_column" | "studio_sql_edit_not_a_table" | "studio_sql_edit_inherited_table" | "studio_sql_edit_no_primary_key" | "studio_sql_edit_key_missing" | "studio_sql_edit_table_read_twice";
66
+ params: Record<string, string>;
67
+ }
68
+ export type CellEditResolution = {
69
+ target: CellEditTarget;
70
+ refusal?: undefined;
71
+ } | {
72
+ target?: undefined;
73
+ refusal: CellEditRefusal;
74
+ };
51
75
  /**
52
- * A collection matched to a table in a SQL query.
76
+ * Which stored cell an edit of the result column `resultColumn` writes, and
77
+ * how its row is found again — or why no edit is safe.
78
+ *
79
+ * Decided on what the database reported, not on the query's text. Each
80
+ * column of the result says which table column it was read from, or that it
81
+ * was computed; a cell is editable only when it was read from a column of a
82
+ * table, every column of that table's primary key was read too, and each of
83
+ * them is the only column of the result with its name — a row holds one value
84
+ * per name.
85
+ *
86
+ * The text was the only evidence before, and it was wrong twice over. For
87
+ * `SELECT p.id, a.name FROM posts p JOIN authors a …` it took the post's `id`
88
+ * for the author's key and updated whichever author shared the post's id.
89
+ * For `SELECT id, lower(email) AS name FROM users` it wrote the edited text
90
+ * to `users.name`.
91
+ *
92
+ * The database reports a table, not which mention of it: in a self-join both
93
+ * sides are `authors`, and a key from one with a value from the other finds
94
+ * the wrong row. So the query is still read, for one thing — the table must
95
+ * be read once, and nothing it is read through may be read twice.
96
+ */
97
+ export declare function resolveCellEdit(sqlText: string, provenance: ResultProvenance, resultColumn: string): CellEditResolution;
98
+ /**
99
+ * A collection whose records a query's rows can be opened as.
53
100
  */
54
101
  export interface ResolvedQueryCollection {
55
- /** DB table name from the SQL AST (e.g. "blog_posts") */
102
+ /** The table, as the database named it. */
56
103
  tableName: string;
57
- /** SQL alias if present (e.g. "bp") */
58
- tableAlias?: string;
104
+ schemaName: string;
59
105
  /** The matched collection */
60
106
  collection: AdminCollection;
61
- /** Columns from this table that are present in the result set */
62
- columns: string[];
63
- /** The result column name that holds the primary key for this table (e.g. "id", "author_id") */
64
- pkColumn?: string;
107
+ /** The result column that holds the table's primary key. */
108
+ pkColumn: string;
65
109
  }
66
110
  /**
67
- * Extract all tables referenced in a SQL query's FROM and JOIN clauses.
68
- * Returns an empty array for non-SELECT queries or parse failures.
111
+ * The collections a result's rows can be opened as: those whose table one of
112
+ * the result's columns was read from, with the table's (single-column) primary
113
+ * key read too, under a name no other column of the result has.
114
+ *
115
+ * Matched on the provenance the database reported. A text match took the
116
+ * result's `id` for every table of a join: `SELECT p.id, a.name FROM posts p
117
+ * JOIN authors a …` offered to open the author whose id was the post's.
69
118
  */
70
- export declare function extractTablesFromQuery(sqlString: string): ExtractedTable[];
71
- import type { AdminCollection } from "@rebasepro/cms-types";
119
+ export declare function resolveQueryCollections(provenance: ResultProvenance, collections: AdminCollection[]): ResolvedQueryCollection[];
72
120
  /**
73
- * Resolve which collections are referenced by a SQL query.
121
+ * The console's "Format SQL": runs of whitespace become one space, a comma is
122
+ * followed by one, and an `=` standing alone gets one on each side — in the
123
+ * code only.
74
124
  *
75
- * Parses the SQL, extracts table names, and matches each against
76
- * registered collections via `collection.table` (falling back to
77
- * snake_case of `collection.slug`).
125
+ * A literal, a quoted identifier and a comment are copied as they are: their
126
+ * text is data, and rewriting `'a,b'` to `'a, b'` changes the value the next
127
+ * Run writes. A line comment keeps the line break that ends it, or it would
128
+ * swallow the code after it. `>=`, `<=`, `!=`, `:=` and `=>` are operators of
129
+ * their own and are left whole.
130
+ */
131
+ export declare function formatSql(sqlText: string): string;
132
+ /**
133
+ * `sqlText` with the console's automatic `LIMIT` on it, or `null` when it
134
+ * takes none (see {@link acceptsAutoLimit}).
78
135
  *
79
- * For each matched collection, determines which result columns
80
- * belong to that table using the database schema information.
136
+ * The limit goes straight after the statement's last token — before a
137
+ * trailing comment, which would otherwise swallow it, and in place of a
138
+ * trailing `;`. A query that computes an aggregate is limited like any other:
139
+ * the limit applies to the rows it returns, so a `count(*)` still counts every
140
+ * row, and a window `count(*) OVER ()` is no longer a way past the cap.
81
141
  */
82
- export declare function resolveQueryCollections(sqlString: string, schemas: Record<string, TableInfo[]>, collections: AdminCollection[], resultColumns?: string[]): ResolvedQueryCollection[];
83
- export interface PKMapping {
84
- /** The actual column name in the database table */
85
- dbColumn: string;
86
- /** The column name as it appears in the query result set (may be aliased) */
87
- resultColumn: string;
88
- }
89
- export interface TableAndPKResult {
90
- tableName?: string;
91
- /**
92
- * The schema the query named for that table — `undefined` when it named
93
- * none. Build the UPDATE with {@link quoteTableName}: a bare table name
94
- * resolves through the search path, which is how editing a row of
95
- * `archive.orders` used to update `public.orders`.
96
- */
97
- schemaName?: string;
98
- primaryKeys?: PKMapping[];
99
- error?: string;
100
- }
101
- export declare function determineTableAndPK(sqlString: string, columnKey: string, schemas: Record<string, TableInfo[]>): TableAndPKResult;
142
+ export declare function withAutoLimit(sqlText: string, limit: number): string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/studio",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Rebase Studio — the SQL editor, schema visualiser, RLS policy editor and API explorer.",
5
5
  "keywords": [
6
6
  "rebase",
@@ -35,19 +35,19 @@
35
35
  "pgsql-ast-parser": "12.0.2",
36
36
  "prism-react-renderer": "^2.4.1",
37
37
  "react-dropzone": "^19.1.1",
38
- "@rebasepro/app": "0.23.0",
39
- "@rebasepro/client": "0.23.0",
40
- "@rebasepro/cms-types": "0.23.0",
41
- "@rebasepro/common": "0.23.0",
42
- "@rebasepro/types": "0.23.0",
43
- "@rebasepro/ui": "0.23.0",
44
- "@rebasepro/utils": "0.23.0"
38
+ "@rebasepro/cms-types": "0.24.0",
39
+ "@rebasepro/app": "0.24.0",
40
+ "@rebasepro/client": "0.24.0",
41
+ "@rebasepro/ui": "0.24.0",
42
+ "@rebasepro/utils": "0.24.0",
43
+ "@rebasepro/common": "0.24.0",
44
+ "@rebasepro/types": "0.24.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "react": "^19.2.7",
48
48
  "react-dom": "^19.2.7",
49
49
  "react-router": "^8.3.0",
50
- "@rebasepro/cms": "0.23.0"
50
+ "@rebasepro/cms": "0.24.0"
51
51
  },
52
52
  "peerDependenciesMeta": {
53
53
  "@rebasepro/cms": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"ApiExplorer-pYMZflL3.js","names":[],"sources":["../src/components/ApiExplorer/parseSpec.ts","../src/components/ApiExplorer/EndpointDetail.tsx","../src/components/ApiExplorer/TryItPanel.tsx","../src/components/ApiExplorer/ApiExplorer.tsx"],"sourcesContent":["import type { OpenApiSpec, ParsedEndpoint, EndpointGroup } from \"./types\";\n\n/**\n * Parse an OpenAPI 3.x spec into grouped, sorted endpoints for the sidebar.\n */\nexport function parseOpenApiSpec(spec: OpenApiSpec): {\n groups: EndpointGroup[];\n allEndpoints: ParsedEndpoint[];\n} {\n const allEndpoints: ParsedEndpoint[] = [];\n const tagMap = new Map<string, ParsedEndpoint[]>();\n\n // Build tag description lookup\n const tagDescriptions = new Map<string, string>();\n for (const t of spec.tags ?? []) {\n tagDescriptions.set(t.name, t.description ?? \"\");\n }\n\n for (const [path, methods] of Object.entries(spec.paths ?? {})) {\n for (const [method, op] of Object.entries(methods)) {\n if ([\"get\", \"post\", \"put\", \"patch\", \"delete\"].indexOf(method) === -1) continue;\n\n const tags = op.tags?.length ? op.tags : [\"Other\"];\n const shortPath = path.replace(/^\\/api\\/data/, \"\");\n\n const endpoint: ParsedEndpoint = {\n id: `${method}:${path}`,\n method,\n path,\n shortPath: shortPath || \"/\",\n summary: op.summary ?? \"\",\n description: op.description ?? \"\",\n tags,\n parameters: op.parameters ?? [],\n requestBody: op.requestBody,\n responses: op.responses ?? {},\n security: op.security,\n operationId: op.operationId\n };\n\n allEndpoints.push(endpoint);\n for (const tag of tags) {\n if (!tagMap.has(tag)) tagMap.set(tag, []);\n tagMap.get(tag)!.push(endpoint);\n }\n }\n }\n\n // Method sort order\n const ORDER: Record<string, number> = { get: 0,\npost: 1,\nput: 2,\npatch: 3,\ndelete: 4 };\n\n const groups: EndpointGroup[] = [];\n // Sort tags: use spec.tags order if available, else alphabetical\n const tagOrder = (spec.tags ?? []).map((t) => t.name);\n const sortedTags = [...tagMap.keys()].sort((a, b) => {\n const ai = tagOrder.indexOf(a);\n const bi = tagOrder.indexOf(b);\n if (ai !== -1 && bi !== -1) return ai - bi;\n if (ai !== -1) return -1;\n if (bi !== -1) return 1;\n return a.localeCompare(b);\n });\n\n for (const tag of sortedTags) {\n const endpoints = tagMap.get(tag)!;\n endpoints.sort((a, b) => {\n const pa = a.path.localeCompare(b.path);\n if (pa !== 0) return pa;\n return (ORDER[a.method] ?? 99) - (ORDER[b.method] ?? 99);\n });\n groups.push({\n tag,\n description: tagDescriptions.get(tag),\n endpoints\n });\n }\n\n return { groups,\nallEndpoints };\n}\n\n/**\n * Resolve a $ref string (e.g. \"#/components/schemas/Author\") to a schema name.\n */\nexport function resolveRefName(ref: string): string {\n const parts = ref.split(\"/\");\n return parts[parts.length - 1];\n}\n\n/**\n * Resolve a $ref to its actual schema from the spec.\n */\nexport function resolveRef(spec: OpenApiSpec, ref: string): unknown {\n const parts = ref.replace(\"#/\", \"\").split(\"/\");\n let current: unknown = spec;\n for (const part of parts) {\n current = (current as Record<string, unknown>)?.[part];\n }\n return current ?? {};\n}\n","import React from \"react\";\nimport {\n ArrowRightFromLineIcon,\n Chip,\n cls,\n defaultBorderMixin,\n iconSize,\n SlidersHorizontalIcon,\n Typography,\n UploadIcon\n} from \"@rebasepro/ui\";\nimport type { ParsedEndpoint, OpenApiSpec, OpenApiSchema } from \"./types\";\nimport { resolveRef, resolveRefName } from \"./parseSpec\";\n\n/**\n * Renders the documentation view for a single API endpoint:\n * parameters, request body schema, response schemas.\n */\nexport function EndpointDetail({ endpoint, spec }: { endpoint: ParsedEndpoint; spec: OpenApiSpec }) {\n return (\n <div className=\"p-6 space-y-8 max-w-4xl\">\n {/* Summary / Description */}\n {(endpoint.summary || endpoint.description) && (\n <div>\n {endpoint.summary && (\n <Typography variant=\"h6\" className=\"font-semibold mb-1\">\n {endpoint.summary}\n </Typography>\n )}\n {endpoint.description && (\n <Typography variant=\"body2\" className=\"text-text-secondary dark:text-text-secondary-dark\">\n {endpoint.description}\n </Typography>\n )}\n </div>\n )}\n\n {/* Parameters */}\n {endpoint.parameters.length > 0 && (\n <section>\n <SectionHeading icon={<SlidersHorizontalIcon size={iconSize.small} className=\"text-text-secondary dark:text-text-secondary-dark\" />} title=\"Parameters\"/>\n <div className={cls(\"rounded-lg border overflow-hidden\", defaultBorderMixin)}>\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-surface-raised text-left\">\n <th className=\"px-4 py-2 font-medium text-text-secondary dark:text-text-secondary-dark\">\n Name\n </th>\n <th className=\"px-4 py-2 font-medium text-text-secondary dark:text-text-secondary-dark\">\n In\n </th>\n <th className=\"px-4 py-2 font-medium text-text-secondary dark:text-text-secondary-dark\">\n Type\n </th>\n <th className=\"px-4 py-2 font-medium text-text-secondary dark:text-text-secondary-dark\">\n Description\n </th>\n </tr>\n </thead>\n <tbody>\n {endpoint.parameters.map((p, i) => (\n <tr\n key={p.name + i}\n className={cls(\"border-t\", defaultBorderMixin)}\n >\n <td className=\"px-4 py-2.5\">\n <code className=\"text-xs font-mono font-semibold\">{p.name}</code>\n {p.required && <span className=\"text-red-500 ml-1 text-xs\">*</span>}\n </td>\n <td className=\"px-4 py-2.5\">\n <Chip\n size=\"smallest\"\n colorScheme={p.in === \"path\" ? \"orangeDarker\" : \"cyanDarker\"}\n >\n {p.in}\n </Chip>\n </td>\n <td className=\"px-4 py-2.5 text-xs font-mono text-text-secondary dark:text-text-secondary-dark\">\n {schemaTypeLabel(p.schema)}\n </td>\n <td className=\"px-4 py-2.5 text-xs text-text-secondary dark:text-text-secondary-dark\">\n {p.description ?? \"—\"}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </section>\n )}\n\n {/* Request Body */}\n {endpoint.requestBody && (\n <section>\n <SectionHeading icon={<UploadIcon size={iconSize.small} className=\"text-text-secondary dark:text-text-secondary-dark\" />} title=\"Request Body\"/>\n {Object.entries(endpoint.requestBody.content ?? {}).map(([contentType, media]) => (\n <div key={contentType}>\n <Chip size=\"smallest\" colorScheme=\"blueDarker\" className=\"mb-3\">\n {contentType}\n </Chip>\n {media.schema && <SchemaBlock schema={media.schema} spec={spec} depth={0}/>}\n </div>\n ))}\n </section>\n )}\n\n {/* Responses */}\n <section>\n <SectionHeading icon={<ArrowRightFromLineIcon size={iconSize.small} className=\"text-text-secondary dark:text-text-secondary-dark\" />} title=\"Responses\"/>\n <div className=\"space-y-3\">\n {Object.entries(endpoint.responses).map(([code, res]) => (\n <div\n key={code}\n className={cls(\"rounded-lg border overflow-hidden\", defaultBorderMixin)}\n >\n <div\n className={cls(\n \"flex items-center gap-3 px-4 py-2.5\",\n \"bg-surface-sheet\"\n )}\n >\n <StatusBadge code={code}/>\n <Typography\n variant=\"body2\"\n className=\"text-text-secondary dark:text-text-secondary-dark text-xs\"\n >\n {res.description}\n </Typography>\n </div>\n {res.content &&\n Object.entries(res.content).map(\n ([ct, media]) =>\n media.schema && (\n <div\n key={ct}\n className={cls(\"px-4 py-3 border-t\", defaultBorderMixin)}\n >\n <SchemaBlock schema={media.schema} spec={spec} depth={0}/>\n </div>\n )\n )}\n </div>\n ))}\n </div>\n </section>\n </div>\n );\n}\n\n/* ── Schema Block ─────────────────────────────────────────────────── */\n\nfunction SchemaBlock({ schema, spec, depth }: { schema: OpenApiSchema; spec: OpenApiSpec; depth: number }) {\n // Resolve $ref\n if (schema.$ref) {\n const name = resolveRefName(schema.$ref);\n const resolved = resolveRef(spec, schema.$ref) as OpenApiSchema;\n return (\n <div>\n <Typography\n variant=\"caption\"\n className=\"text-primary dark:text-primary-dark font-mono text-xs mb-2 block\"\n >\n {name}\n </Typography>\n <SchemaBlock schema={resolved} spec={spec} depth={depth}/>\n </div>\n );\n }\n\n // Object with properties\n if (schema.properties) {\n const required = new Set(schema.required ?? []);\n return (\n <div\n className={cls(\n \"rounded-lg overflow-hidden\",\n depth > 0 && `border ml-4 mt-1 ${defaultBorderMixin}`\n )}\n >\n <table className=\"w-full text-xs\">\n <tbody>\n {Object.entries(schema.properties).map(([key, prop]) => (\n <tr\n key={key}\n className={cls(\"border-t first:border-t-0\", defaultBorderMixin)}\n >\n <td className=\"px-3 py-2 align-top w-36\">\n <code className=\"font-mono font-semibold text-text-primary dark:text-text-primary-dark\">\n {key}\n </code>\n {required.has(key) && <span className=\"text-red-500 ml-0.5\">*</span>}\n {prop.readOnly && (\n <span className=\"ml-1.5 text-[9px] text-text-secondary dark:text-text-secondary-dark italic\">\n read-only\n </span>\n )}\n </td>\n <td className=\"px-3 py-2 align-top w-28\">\n <span className=\"font-mono text-text-secondary dark:text-text-secondary-dark\">\n {schemaTypeLabel(prop)}\n </span>\n </td>\n <td className=\"px-3 py-2 align-top text-text-secondary dark:text-text-secondary-dark\">\n {prop.description ?? \"\"}\n {prop.enum && (\n <div className=\"flex flex-wrap gap-1 mt-1\">\n {prop.enum.map((v) => (\n <span\n key={String(v)}\n className=\"px-1.5 py-0.5 rounded bg-surface-raised text-[10px] font-mono\"\n >\n {String(v)}\n </span>\n ))}\n </div>\n )}\n {prop.properties && <SchemaBlock schema={prop} spec={spec} depth={depth + 1}/>}\n {prop.$ref && <SchemaBlock schema={prop} spec={spec} depth={depth + 1}/>}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n }\n\n // Array\n if (schema.type === \"array\" && schema.items) {\n return (\n <div>\n <span className=\"font-mono text-xs text-text-secondary dark:text-text-secondary-dark\">Array of:</span>\n <SchemaBlock schema={schema.items} spec={spec} depth={depth + 1}/>\n </div>\n );\n }\n\n // Primitive\n return (\n <span className=\"font-mono text-xs text-text-secondary dark:text-text-secondary-dark\">\n {schemaTypeLabel(schema)}\n </span>\n );\n}\n\n/* ── Helpers ──────────────────────────────────────────────────────── */\n\nfunction schemaTypeLabel(schema?: OpenApiSchema): string {\n if (!schema) return \"any\";\n if (schema.$ref) return resolveRefName(schema.$ref);\n if (schema.type === \"array\") return `${schemaTypeLabel(schema.items)}[]`;\n if (schema.format) return `${schema.type} (${schema.format})`;\n return schema.type ?? \"object\";\n}\n\nfunction SectionHeading({ icon, title }: { icon: React.ReactNode; title: string }) {\n return (\n <div className=\"flex items-center gap-2 mb-3\">\n {icon}\n <Typography variant=\"subtitle2\" className=\"font-semibold text-sm\">\n {title}\n </Typography>\n </div>\n );\n}\n\nfunction StatusBadge({ code }: { code: string }) {\n const n = parseInt(code, 10);\n const color =\n n < 300\n ? \"text-emerald-600 dark:text-emerald-400\"\n : n < 400\n ? \"text-blue-600 dark:text-blue-400\"\n : n < 500\n ? \"text-amber-600 dark:text-amber-400\"\n : \"text-red-600 dark:text-red-400\";\n\n return <span className={cls(\"text-xs font-semibold font-mono\", color)}>{code}</span>;\n}\n","import React, { useState, useCallback, useMemo, useEffect } from \"react\";\nimport {\n Button,\n cls,\n defaultBorderMixin,\n IconButton,\n iconSize,\n LoaderIcon,\n PlusIcon,\n SendIcon,\n TextField,\n Typography,\n XIcon\n} from \"@rebasepro/ui\";\nimport { useRebaseContext, UserSelectPopover, SelectableUser } from \"@rebasepro/app\";\nimport { AuthSimulationSelector } from \"../AuthSimulationSelector\";\nimport type { ParsedEndpoint } from \"./types\";\nimport type { User } from \"@rebasepro/types\";\n\ninterface TryItPanelProps {\n endpoint: ParsedEndpoint;\n apiUrl: string;\n getAuthToken: () => Promise<string | null | undefined>;\n user: User | null;\n basePath?: string;\n}\n\n/**\n * Interactive \"Try It\" panel that lets the user execute API requests\n * using their current JWT token directly from the Studio.\n */\nexport function TryItPanel({ endpoint, apiUrl, getAuthToken, user, basePath = \"\" }: TryItPanelProps) {\n const storageKey = `rebase_apiexplorer_${endpoint.method}_${endpoint.path}`;\n\n const [pathParams, setPathParams] = useState<Record<string, string>>(() => {\n try { const v = localStorage.getItem(`${storageKey}_path`); if (v) return JSON.parse(v); } catch { /* ignore */ }\n return {};\n });\n const [queryParams, setQueryParams] = useState<Record<string, string>>(() => {\n try { const v = localStorage.getItem(`${storageKey}_query`); if (v) return JSON.parse(v); } catch { /* ignore */ }\n return {};\n });\n const [customHeaders, setCustomHeaders] = useState<Array<{ key: string; value: string }>>(() => {\n try { const v = localStorage.getItem(`${storageKey}_headers`); if (v) return JSON.parse(v); } catch { /* ignore */ }\n // None until the caller adds one: every header here is sent.\n return [];\n });\n const [body, setBody] = useState(() => {\n try { const v = localStorage.getItem(`${storageKey}_body`); if (v) return JSON.parse(v); } catch { /* ignore */ }\n return buildBodyTemplate(endpoint);\n });\n const [response, setResponse] = useState<{ status: number; statusText: string; body: string; time: number } | null>(\n null\n );\n const [loading, setLoading] = useState(false);\n const [authMode, setAuthMode] = useState<\"jwt\" | \"none\">(\"jwt\");\n const [validationError, setValidationError] = useState<string | null>(null);\n\n const rebaseContext = useRebaseContext();\n const currentUser = rebaseContext.authController?.user;\n\n const users = useMemo((): SelectableUser[] => {\n const managed: SelectableUser[] = [];\n if (currentUser) {\n managed.push({\n uid: currentUser.uid,\n displayName: currentUser.displayName,\n email: currentUser.email,\n photoURL: currentUser.photoURL,\n roles: currentUser.roles\n });\n }\n return managed;\n }, [currentUser]);\n\n const currentSelectableUser = useMemo((): SelectableUser | null => {\n if (!currentUser) return null;\n return {\n uid: currentUser.uid,\n displayName: currentUser.displayName,\n email: currentUser.email,\n photoURL: currentUser.photoURL,\n roles: currentUser.roles\n };\n }, [currentUser]);\n\n const [selectedUser, setSelectedUser] = useState<SelectableUser | null>(null);\n\n useEffect(() => {\n localStorage.setItem(`${storageKey}_path`, JSON.stringify(pathParams));\n localStorage.setItem(`${storageKey}_query`, JSON.stringify(queryParams));\n localStorage.setItem(`${storageKey}_headers`, JSON.stringify(customHeaders));\n localStorage.setItem(`${storageKey}_body`, JSON.stringify(body));\n }, [storageKey, pathParams, queryParams, customHeaders, body]);\n\n // Build the final URL\n const resolvedUrl = useMemo(() => {\n const base = basePath.startsWith(\"/\") ? basePath : `/${basePath}`;\n const cleanBase = base === \"/\" ? \"\" : base;\n let url = `${apiUrl.replace(/\\/+$/, \"\")}${cleanBase}${endpoint.path}`;\n // Replace path params\n for (const [key, val] of Object.entries(pathParams)) {\n url = url.replace(`{${key}}`, encodeURIComponent(val));\n }\n // Append query params\n const qp = Object.entries(queryParams).filter(([, v]) => v.trim() !== \"\");\n if (qp.length > 0) {\n url += \"?\" + qp.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join(\"&\");\n }\n return url;\n }, [apiUrl, basePath, endpoint.path, pathParams, queryParams]);\n\n const pathParamDefs = endpoint.parameters.filter((p) => p.in === \"path\");\n const queryParamDefs = endpoint.parameters.filter((p) => p.in === \"query\");\n const hasBody = [\"post\", \"put\", \"patch\"].includes(endpoint.method);\n\n const execute = useCallback(async () => {\n setValidationError(null);\n if (hasBody && body.trim()) {\n try {\n JSON.parse(body);\n } catch (err: unknown) {\n setValidationError(`Invalid JSON: ${err instanceof Error ? err.message : String(err)}`);\n return;\n }\n }\n\n setLoading(true);\n setResponse(null);\n const start = performance.now();\n\n try {\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n\n for (const h of customHeaders) {\n if (h.key.trim()) headers[h.key.trim()] = h.value;\n }\n\n if (authMode === \"jwt\") {\n const token = await getAuthToken();\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n if (selectedUser && selectedUser.uid !== currentUser?.uid) {\n headers[\"x-rebase-impersonate\"] = selectedUser.uid;\n }\n }\n\n const res = await fetch(resolvedUrl, {\n method: endpoint.method.toUpperCase(),\n headers,\n body: hasBody && body.trim() ? body : undefined\n });\n\n const elapsed = Math.round(performance.now() - start);\n let text: string;\n\n const rawText = await res.text();\n try {\n const json = JSON.parse(rawText);\n text = JSON.stringify(json, null, 2);\n } catch {\n text = rawText;\n }\n\n setResponse({ status: res.status,\nstatusText: res.statusText,\nbody: text,\ntime: elapsed });\n } catch (err: unknown) {\n setResponse({\n status: 0,\n statusText: \"Network Error\",\n body: err instanceof Error ? err.message : \"Request failed\",\n time: Math.round(performance.now() - start)\n });\n } finally {\n setLoading(false);\n }\n }, [resolvedUrl, endpoint.method, hasBody, body, authMode, getAuthToken, customHeaders, selectedUser, currentUser?.uid]);\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"p-5 space-y-5 overflow-y-auto flex-1\">\n {/* Auth Mode */}\n <AuthSimulationSelector\n authMode={authMode}\n setAuthMode={setAuthMode}\n selectedUser={selectedUser}\n setSelectedUser={setSelectedUser}\n users={users}\n loading={false}\n currentUser={currentSelectableUser}\n />\n\n {/* Path Params */}\n {pathParamDefs.length > 0 && (\n <ParamSection\n title=\"Path Parameters\"\n params={pathParamDefs}\n values={pathParams}\n onChange={(k, v) => setPathParams((prev) => ({ ...prev,\n[k]: v }))}\n />\n )}\n\n {/* Query Params */}\n {queryParamDefs.length > 0 && (\n <ParamSection\n title=\"Query Parameters\"\n params={queryParamDefs}\n values={queryParams}\n onChange={(k, v) => setQueryParams((prev) => ({ ...prev,\n[k]: v }))}\n />\n )}\n\n {/* Custom Headers */}\n <CustomKeyValueSection\n title=\"Custom Headers\"\n values={customHeaders}\n onChange={(i, k, v) => {\n const next = [...customHeaders];\n next[i] = { key: k,\nvalue: v };\n setCustomHeaders(next);\n }}\n onAdd={() => setCustomHeaders((prev) => [...prev, { key: \"\",\nvalue: \"\" }])}\n onRemove={(i) => {\n const next = [...customHeaders];\n next.splice(i, 1);\n setCustomHeaders(next);\n }}\n />\n\n {/* Request Body */}\n {hasBody && (\n <div>\n <Typography\n variant=\"caption\"\n className=\"text-text-secondary dark:text-text-secondary-dark text-xs font-semibold uppercase tracking-wider mb-2 block\"\n >\n Request Body\n </Typography>\n <TextField\n multiline\n minRows={10}\n aria-label=\"Request Body\"\n value={body}\n onChange={(e) => { setBody(e.target.value); setValidationError(null); }}\n spellCheck={false}\n error={!!validationError}\n className=\"w-full\"\n inputClassName=\"font-mono text-xs p-3 resize-y\"\n />\n {validationError && (\n <Typography variant=\"caption\" className=\"text-red-500 mt-1 block text-xs\">\n {validationError}\n </Typography>\n )}\n </div>\n )}\n\n {/* URL Preview */}\n <div className=\"rounded-lg bg-surface-raised p-3\">\n <Typography\n variant=\"caption\"\n className=\"text-text-secondary dark:text-text-secondary-dark text-[10px] uppercase tracking-wider block mb-1\"\n >\n Request URL\n </Typography>\n <code className=\"text-xs font-mono text-text-primary dark:text-text-primary-dark break-all\">\n {resolvedUrl}\n </code>\n </div>\n\n {/* Execute Button */}\n <Button variant=\"filled\" onClick={execute} disabled={loading} className=\"w-full\">\n {loading ? (\n <span className=\"flex items-center gap-2\">\n <LoaderIcon size={iconSize.small} className=\"animate-spin\" />\n Sending…\n </span>\n ) : (\n <span className=\"flex items-center gap-2\">\n <SendIcon size={iconSize.small} />\n Send Request\n </span>\n )}\n </Button>\n\n {/* Response */}\n {response && (\n <div className={cls(\"rounded-lg border overflow-hidden\", defaultBorderMixin)}>\n <div\n className={cls(\n \"flex items-center justify-between px-4 py-2.5\",\n \"bg-surface-sheet\"\n )}\n >\n <div className=\"flex items-center gap-3\">\n <ResponseBadge status={response.status}/>\n <Typography\n variant=\"caption\"\n className=\"text-text-secondary dark:text-text-secondary-dark text-xs\"\n >\n {response.statusText}\n </Typography>\n </div>\n <Typography\n variant=\"caption\"\n className=\"text-text-secondary dark:text-text-secondary-dark text-xs font-mono\"\n >\n {response.time}ms\n </Typography>\n </div>\n <pre\n className={cls(\n \"p-4 text-xs font-mono overflow-auto max-h-96\",\n \"bg-surface-well text-emerald-700 dark:text-emerald-400\"\n )}\n >\n {response.body}\n </pre>\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/* ── Param Section ────────────────────────────────────────────────── */\n\nfunction ParamSection({\n title,\n params,\n values,\n onChange\n}: {\n title: string;\n params: ParsedEndpoint[\"parameters\"];\n values: Record<string, string>;\n onChange: (_key: string, _value: string) => void;\n}) {\n const paramIdBase = React.useId();\n return (\n <div>\n <Typography\n variant=\"caption\"\n className=\"text-text-secondary dark:text-text-secondary-dark text-xs font-semibold uppercase tracking-wider mb-2 block\"\n >\n {title}\n </Typography>\n <div className=\"space-y-2\">\n {params.map((p, index) => (\n <div key={p.name} className=\"flex items-center gap-3\">\n <label className=\"w-32 shrink-0\" htmlFor={`${paramIdBase}-${index}-param`}>\n <code className=\"text-xs font-mono font-semibold\">{p.name}</code>\n {p.required && <span className=\"text-red-500 ml-0.5 text-xs\">*</span>}\n </label>\n <TextField\n id={`${paramIdBase}-${index}-param`}\n size=\"small\"\n placeholder={p.description ?? p.name}\n value={values[p.name] ?? \"\"}\n onChange={(e) => onChange(p.name, e.target.value)}\n className=\"flex-1\"\n inputClassName=\"font-mono text-xs\"\n />\n </div>\n ))}\n </div>\n </div>\n );\n}\n\nfunction CustomKeyValueSection({\n title,\n values,\n onChange,\n onAdd,\n onRemove\n}: {\n title: string;\n values: Array<{ key: string; value: string }>;\n onChange: (index: number, key: string, value: string) => void;\n onAdd: () => void;\n onRemove: (index: number) => void;\n}) {\n return (\n <div>\n <div className=\"flex items-center justify-between mb-2\">\n <Typography\n variant=\"caption\"\n className=\"text-text-secondary dark:text-text-secondary-dark text-xs font-semibold uppercase tracking-wider block\"\n >\n {title}\n </Typography>\n <Button\n variant=\"text\"\n size=\"small\"\n color=\"primary\"\n onClick={onAdd}\n className=\"text-xs p-0 min-h-0\"\n >\n <PlusIcon size={iconSize.small} className=\"mr-1\" /> Add Header\n </Button>\n </div>\n <div className=\"space-y-2\">\n {values.map((v, i) => (\n <div key={i} className=\"flex items-center gap-2\">\n <TextField\n size=\"small\"\n aria-label={`Header ${i + 1} name`}\n placeholder=\"Header name\"\n value={v.key}\n onChange={(e) => onChange(i, e.target.value, v.value)}\n className=\"w-1/3\"\n inputClassName=\"font-mono text-xs\"\n />\n <TextField\n size=\"small\"\n aria-label={`Header ${i + 1} value`}\n placeholder=\"Value\"\n value={v.value}\n onChange={(e) => onChange(i, v.key, e.target.value)}\n className=\"flex-1\"\n inputClassName=\"font-mono text-xs\"\n />\n <IconButton\n size=\"small\"\n onClick={() => onRemove(i)}\n className=\"text-text-secondary hover:text-red-500 shrink-0\"\n title=\"Remove\"\n >\n <XIcon size={iconSize.small} />\n </IconButton>\n </div>\n ))}\n {values.length === 0 && (\n <Typography variant=\"caption\" className=\"text-text-secondary/50 italic text-xs\">\n No custom headers added.\n </Typography>\n )}\n </div>\n </div>\n );\n}\n\n/* ── Helpers ──────────────────────────────────────────────────────── */\n\nfunction ResponseBadge({ status }: { status: number }) {\n let colorClass = \"text-red-500\";\n if (status >= 200 && status < 300) colorClass = \"text-emerald-500\";\n else if (status >= 300 && status < 400) colorClass = \"text-blue-500\";\n else if (status >= 400 && status < 500) colorClass = \"text-amber-500\";\n\n return (\n <span className={cls(\"text-xs font-semibold font-mono\", colorClass)}>\n {status || \"ERR\"}\n </span>\n );\n}\n\nfunction buildBodyTemplate(endpoint: ParsedEndpoint): string {\n if (!endpoint.requestBody?.content) return \"{\\n \\n}\";\n const json = endpoint.requestBody.content[\"application/json\"];\n if (!json?.schema?.properties) return \"{\\n \\n}\";\n\n const props = json.schema.properties;\n const lines: string[] = [\"{\"];\n const keys = Object.keys(props);\n keys.forEach((key, i) => {\n const prop = props[key];\n if (prop.readOnly) return;\n const comma = i < keys.length - 1 ? \",\" : \"\";\n const val = defaultValue(prop);\n lines.push(` \"${key}\": ${val}${comma}`);\n });\n lines.push(\"}\");\n return lines.join(\"\\n\");\n}\n\nfunction defaultValue(schema: { type?: string; format?: string; enum?: (string | number)[] }): string {\n if (schema.enum) return JSON.stringify(schema.enum[0]);\n switch (schema.type) {\n case \"string\":\n return schema.format === \"date-time\" ? `\"${new Date().toISOString()}\"` : '\"\"';\n case \"number\":\n case \"integer\":\n return \"0\";\n case \"boolean\":\n return \"false\";\n case \"array\":\n return \"[]\";\n default:\n return \"null\";\n }\n}\n","import React, { useState, useEffect, useMemo } from \"react\";\nimport { useApiBase, useApiConfig, useAuthController } from \"@rebasepro/app\";\nimport {\n CircularProgress,\n Typography,\n Alert,\n cls,\n Button,\n Chip,\n defaultBorderMixin,\n SearchBar,\n iconSize\n} from \"@rebasepro/ui\";\nimport { BookOpenIcon, PlayIcon } from \"@rebasepro/ui\";\nimport { EndpointDetail } from \"./EndpointDetail\";\nimport { TryItPanel } from \"./TryItPanel\";\nimport type { OpenApiSpec, ParsedEndpoint } from \"./types\";\nimport { parseOpenApiSpec } from \"./parseSpec\";\n\n/**\n * Custom-built API Explorer for Rebase Studio.\n * No external dependencies — renders the OpenAPI spec natively\n * with deep integration into the Rebase auth system.\n */\nexport function ApiExplorer() {\n const apiConfig = useApiConfig();\n const apiBase = useApiBase();\n const authController = useAuthController();\n const apiUrl = apiConfig?.apiUrl;\n\n const [spec, setSpec] = useState<OpenApiSpec | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const [selectedEndpoint, setSelectedEndpoint] = useState<ParsedEndpoint | null>(null);\n const [sidebarFilter, setSidebarFilter] = useState(\"\");\n const [tryItOpen, setTryItOpen] = useState(false);\n\n // Fetch OpenAPI spec\n useEffect(() => {\n if (!apiUrl) return;\n let cancelled = false;\n const specUrl = `${apiBase}/docs`;\n\n (async () => {\n try {\n // The spec endpoint itself is public on a stock backend, but\n // `apiUrl` may route through an authenticated proxy (the\n // console's Studio embed) — carry the token like every other\n // request in this view, and like LogsExplorer does.\n const getAuthToken = apiConfig?.getAuthToken ?? authController.getAuthToken;\n const token = getAuthToken ? await getAuthToken() : null;\n const res = await fetch(specUrl, token ? { headers: { Authorization: `Bearer ${token}` } } : undefined);\n if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);\n const data = await res.json();\n if (!cancelled) {\n setSpec(data);\n setLoading(false);\n }\n } catch (err: unknown) {\n if (!cancelled) {\n setError(err instanceof Error ? err.message : \"Failed to load API spec\");\n setLoading(false);\n }\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [apiUrl, apiBase, apiConfig, authController]);\n\n // Parse spec into grouped endpoints\n const { groups, allEndpoints } = useMemo(() => {\n if (!spec) return { groups: [],\nallEndpoints: [] };\n return parseOpenApiSpec(spec);\n }, [spec]);\n\n // Filter\n const filteredGroups = useMemo(() => {\n if (!sidebarFilter.trim()) return groups;\n const q = sidebarFilter.toLowerCase();\n return groups\n .map((g) => ({\n ...g,\n endpoints: g.endpoints.filter(\n (e) =>\n e.path.toLowerCase().includes(q) ||\n e.summary.toLowerCase().includes(q) ||\n e.method.toLowerCase().includes(q)\n )\n }))\n .filter((g) => g.endpoints.length > 0);\n }, [groups, sidebarFilter]);\n\n // Auto-select first endpoint\n useEffect(() => {\n if (!selectedEndpoint && allEndpoints.length > 0) {\n setSelectedEndpoint(allEndpoints[0]);\n }\n }, [allEndpoints, selectedEndpoint]);\n\n // ── States ───────────────────────────────────────────────────────\n if (!apiUrl) {\n return (\n <div className=\"flex items-center justify-center h-full w-full p-8\">\n <Alert color=\"warning\">\n <Typography variant=\"body2\">\n No API URL configured. Ensure your app provides an{\" \"}\n <code className=\"font-mono text-xs\">apiUrl</code>.\n </Typography>\n </Alert>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full w-full gap-4\">\n <CircularProgress size=\"medium\"/>\n <Typography variant=\"body2\" className=\"text-text-secondary dark:text-text-secondary-dark animate-pulse\">\n Loading API specification…\n </Typography>\n </div>\n );\n }\n\n if (error || !spec) {\n // The API spec fetch rejects with whatever the transport says, and for\n // an expired console session that is the string \"401\". Rendering it\n // verbatim produced the worst screen in the console: a red box\n // containing three digits, no title, no cause, no way out — on the same\n // product whose storage browser writes four sentences explaining which\n // authorization hook refused a listing.\n //\n // The raw text is kept, below the sentence, because it is what someone\n // pastes into a bug report. It is just no longer the whole message.\n const status = (error ?? \"\").trim();\n const isAuth = /^(401|403)\\b/.test(status) || /unauthori[sz]ed|forbidden/i.test(status);\n return (\n <div className=\"flex items-center justify-center h-full w-full p-8\">\n <div className=\"max-w-md text-center\">\n <Typography variant=\"subtitle2\" className=\"block text-text-primary dark:text-text-primary-dark\">\n {isAuth ? \"This session cannot read the API specification\" : \"Could not load the API specification\"}\n </Typography>\n <Typography variant=\"body2\" className=\"block mt-2 text-text-secondary dark:text-text-secondary-dark\">\n {isAuth\n ? \"The console's access to this project's API was refused. It usually means the session expired — sign in again — or that console access has been turned off for this project in Settings.\"\n : \"The project answered, but not with a specification this explorer could read. Its API is unaffected; only this page is.\"}\n </Typography>\n <Button\n variant=\"outlined\"\n color=\"neutral\"\n size=\"small\"\n className=\"mt-4\"\n onClick={() => window.location.reload()}\n >\n Try again\n </Button>\n {status && (\n <Typography variant=\"caption\" className=\"block mt-4 font-mono text-text-disabled dark:text-text-disabled-dark\">\n {status}\n </Typography>\n )}\n </div>\n </div>\n );\n }\n\n const METHOD_COLORS: Record<string, string> = {\n get: \"text-blue-600 dark:text-blue-400\",\n post: \"text-emerald-600 dark:text-emerald-400\",\n put: \"text-amber-600 dark:text-amber-400\",\n patch: \"text-orange-600 dark:text-orange-400\",\n delete: \"text-red-600 dark:text-red-400\"\n };\n\n return (\n <div className=\"flex h-full w-full overflow-hidden\">\n {/* ── Sidebar ──────────────────────────────────────── */}\n <div\n className={cls(\n \"w-72 min-w-[272px] flex flex-col h-full overflow-hidden border-r\",\n defaultBorderMixin,\n \"bg-surface-sheet\"\n )}\n >\n {/* Header */}\n <div className=\"p-4 space-y-3\">\n <div className=\"flex items-center gap-2\">\n <BookOpenIcon size={iconSize.medium} className=\"text-primary dark:text-primary-dark\" />\n <Typography variant=\"subtitle2\" className=\"font-semibold\">\n {spec.info?.title ?? \"API Reference\"}\n </Typography>\n </div>\n {spec.info?.version && (\n <Chip size=\"smallest\" colorScheme=\"cyanDarker\">\n v{spec.info.version}\n </Chip>\n )}\n {/* Search */}\n <div className=\"mb-2\">\n <SearchBar\n placeholder=\"Filter endpoints…\"\n size=\"small\"\n onTextSearch={(val) => setSidebarFilter(val ?? \"\")}\n />\n </div>\n\n {/* Auth status removed to avoid redundancy with the AuthSimulationSelector */}\n </div>\n\n {/* Endpoint list */}\n <div className=\"flex-1 overflow-y-auto px-2 pb-4\">\n {filteredGroups.map((group) => (\n <div key={group.tag} className=\"mb-3\">\n <Typography\n variant=\"caption\"\n className=\"px-2 py-1.5 text-text-secondary dark:text-text-secondary-dark uppercase tracking-wider font-semibold text-[10px]\"\n >\n {group.tag}\n </Typography>\n {group.endpoints.map((ep) => {\n const isSelected = selectedEndpoint?.id === ep.id;\n return (\n <Button\n key={ep.id}\n variant=\"text\"\n color=\"neutral\"\n fullWidth\n onClick={() => {\n setSelectedEndpoint(ep);\n setTryItOpen(false);\n }}\n className={cls(\n \"!justify-between !px-2.5 !py-1.5 !text-left !text-sm\",\n isSelected\n ? \"bg-surface-raised font-medium\"\n : \"text-text-primary dark:text-text-primary-dark\"\n )}\n >\n <span className=\"truncate text-[13px] opacity-90\">{ep.summary || ep.shortPath}</span>\n <span\n className={cls(\n \"text-[10px] font-semibold uppercase shrink-0\",\n METHOD_COLORS[ep.method] ?? \"text-text-secondary\"\n )}\n >\n {ep.method}\n </span>\n </Button>\n );\n })}\n </div>\n ))}\n {filteredGroups.length === 0 && (\n <Typography\n variant=\"body2\"\n className=\"text-center text-text-secondary dark:text-text-secondary-dark py-8\"\n >\n No endpoints match\n </Typography>\n )}\n </div>\n </div>\n\n {/* ── Main content ─────────────────────────────────── */}\n <div className=\"flex-1 flex flex-col h-full overflow-hidden\">\n {selectedEndpoint ? (\n <>\n {/* Top bar */}\n <div\n className={cls(\n \"flex items-center justify-between px-5 py-3 gap-4 shrink-0 border-b z-10\",\n defaultBorderMixin,\n \"bg-surface-sheet/90 backdrop-blur-md sticky top-0\"\n )}\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <span\n className={cls(\n \"text-xs font-semibold uppercase\",\n METHOD_COLORS[selectedEndpoint.method] ?? \"\"\n )}\n >\n {selectedEndpoint.method}\n </span>\n <code className=\"text-sm font-mono text-text-primary dark:text-text-primary-dark truncate\">\n {selectedEndpoint.path}\n </code>\n </div>\n <Button\n variant={tryItOpen ? \"filled\" : \"outlined\"}\n size=\"small\"\n onClick={() => setTryItOpen((v) => !v)}\n >\n <PlayIcon size={iconSize.small} className=\"mr-1\" />\n Try It\n </Button>\n </div>\n\n {/* Body */}\n <div className=\"flex-1 overflow-y-auto\">\n {tryItOpen ? (\n <TryItPanel\n key={selectedEndpoint.operationId || selectedEndpoint.path}\n endpoint={selectedEndpoint}\n apiUrl={apiUrl}\n getAuthToken={apiConfig?.getAuthToken ?? authController.getAuthToken}\n user={authController.user}\n basePath={spec?.servers?.[0]?.url || \"\"}\n />\n ) : (\n <EndpointDetail endpoint={selectedEndpoint} spec={spec}/>\n )}\n </div>\n </>\n ) : (\n <div className=\"flex items-center justify-center h-full\">\n <Typography variant=\"body2\" className=\"text-text-secondary dark:text-text-secondary-dark\">\n Select an endpoint from the sidebar\n </Typography>\n </div>\n )}\n </div>\n </div>\n );\n}\n\nApiExplorer.displayName = \"ApiExplorer\";\n\n"],"mappings":"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"}