@base44/vite-plugin 1.0.28 → 1.0.31

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 (34) hide show
  1. package/dist/capabilities/inline-edit/controller.d.ts.map +1 -1
  2. package/dist/capabilities/inline-edit/controller.js +2 -5
  3. package/dist/capabilities/inline-edit/controller.js.map +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +18 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/injections/unhandled-errors-handlers.d.ts +2 -1
  8. package/dist/injections/unhandled-errors-handlers.d.ts.map +1 -1
  9. package/dist/injections/unhandled-errors-handlers.js +34 -19
  10. package/dist/injections/unhandled-errors-handlers.js.map +1 -1
  11. package/dist/injections/utils.d.ts +6 -0
  12. package/dist/injections/utils.d.ts.map +1 -1
  13. package/dist/injections/utils.js +8 -0
  14. package/dist/injections/utils.js.map +1 -1
  15. package/dist/injections/visual-edit-agent.d.ts.map +1 -1
  16. package/dist/injections/visual-edit-agent.js +2 -5
  17. package/dist/injections/visual-edit-agent.js.map +1 -1
  18. package/dist/processors/collection-item-field-processor.d.ts.map +1 -1
  19. package/dist/processors/collection-item-field-processor.js +11 -0
  20. package/dist/processors/collection-item-field-processor.js.map +1 -1
  21. package/dist/statics/index.mjs +7 -7
  22. package/dist/statics/index.mjs.map +1 -1
  23. package/dist/visual-edit-plugin.d.ts.map +1 -1
  24. package/dist/visual-edit-plugin.js +20 -22
  25. package/dist/visual-edit-plugin.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/capabilities/inline-edit/controller.ts +2 -6
  28. package/src/index.ts +21 -2
  29. package/src/injections/unhandled-errors-handlers.ts +36 -19
  30. package/src/injections/utils.ts +9 -0
  31. package/src/injections/visual-edit-agent.ts +2 -6
  32. package/src/processors/collection-item-field-processor.ts +12 -0
  33. package/src/visual-edit-plugin.md +13 -13
  34. package/src/visual-edit-plugin.ts +22 -26
@@ -1 +1 @@
1
- {"version":3,"file":"visual-edit-plugin.d.ts","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,wBAAgB,gBAAgB,IAwEzB,MAAM,CACZ"}
1
+ {"version":3,"file":"visual-edit-plugin.d.ts","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAa,MAAM,EAAc,MAAM,MAAM,CAAC;AAI1D,wBAAgB,gBAAgB,IA+EzB,MAAM,CACZ"}
@@ -7,7 +7,13 @@ import { JSXUtils } from "./jsx-utils.js";
7
7
  export function visualEditPlugin() {
8
8
  return {
9
9
  name: "visual-edit-transform",
10
- apply: (config) => config.mode === "development",
10
+ // Dev SERVER only: the injected data-* markers are consumed by the
11
+ // visual-edit agent in the live preview, which vite serves — never by
12
+ // build output. Gating on mode alone also caught `vite build --mode
13
+ // development` (the platform's validation build), where instrumenting
14
+ // user code is pure risk: an injection bug there fails the build on
15
+ // code that doesn't exist on disk.
16
+ apply: (config, env) => env.command === "serve" && config.mode === "development",
11
17
  enforce: "pre",
12
18
  order: "pre",
13
19
  transformIndexHtml(html) {
@@ -73,28 +79,20 @@ export function visualEditPlugin() {
73
79
  },
74
80
  };
75
81
  }
82
+ // Emit the canonical real path (src/pages/Home.jsx) the backend and sandbox use, NOT a
83
+ // legacy alias (pages/Home). The path is anchored on the last "src" segment with the
84
+ // extension retained, so consumers get the file's true sandbox path directly.
76
85
  function extractFilename(id) {
77
- const pathParts = id.split("/");
78
- const segmentIndex = findSegmentIndex(pathParts, ["pages", "components"]);
79
- if (segmentIndex >= 0 && segmentIndex < pathParts.length - 1) {
80
- const relevantParts = pathParts.slice(segmentIndex);
81
- const last = relevantParts[relevantParts.length - 1];
82
- relevantParts[relevantParts.length - 1] = stripExtension(last ?? "");
83
- return relevantParts.join("/");
86
+ // Drop any Vite query suffix (e.g. "?v=...") and normalize separators.
87
+ const cleanId = id.split("?")[0].replace(/\\/g, "/");
88
+ const parts = cleanId.split("/");
89
+ // Anchor on the LAST "src" segment so a "src" elsewhere in the absolute path
90
+ // (e.g. a user's home dir) can't mis-anchor. base44 apps keep all source under src/.
91
+ const srcIndex = parts.lastIndexOf("src");
92
+ if (srcIndex >= 0) {
93
+ return parts.slice(srcIndex).join("/"); // src/pages/Home.jsx, src/Layout.jsx, src/components/ui/Card.tsx
84
94
  }
85
- const lastPart = pathParts[pathParts.length - 1] ?? "";
86
- return stripExtension(lastPart);
87
- }
88
- function findSegmentIndex(parts, segments) {
89
- for (const segment of segments) {
90
- const idx = parts.findIndex((part) => part === segment);
91
- if (idx >= 0)
92
- return idx;
93
- }
94
- return -1;
95
- }
96
- function stripExtension(filename) {
97
- const dotIndex = filename.indexOf(".");
98
- return dotIndex >= 0 ? filename.substring(0, dotIndex) : filename;
95
+ // Fallback: file not under src/ use the bare filename (extension kept).
96
+ return parts[parts.length - 1] ?? "";
99
97
  }
100
98
  //# sourceMappingURL=visual-edit-plugin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"visual-edit-plugin.js","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"visual-edit-plugin.js","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44/vite-plugin",
3
- "version": "1.0.28",
3
+ "version": "1.0.31",
4
4
  "description": "The Vite plugin for base44 based applications",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -6,7 +6,7 @@ import {
6
6
  shouldEnterInlineEditingMode,
7
7
  isStaticArrayTextElement,
8
8
  } from "./dom-utils.js";
9
- import { PLUGIN_ELEMENT_ATTR } from "../../injections/utils.js";
9
+ import { PLUGIN_ELEMENT_ATTR, readElementClasses } from "../../injections/utils.js";
10
10
 
11
11
  const DEBOUNCE_MS = 500;
12
12
 
@@ -36,17 +36,13 @@ export function createInlineEditController(
36
36
  const originalContent = element.dataset.originalTextContent;
37
37
  const newContent = element.textContent;
38
38
 
39
- const svgElement = element as unknown as SVGElement;
40
39
  const rect = element.getBoundingClientRect();
41
40
 
42
41
  const message: Record<string, unknown> = {
43
42
  type: "inline-edit",
44
43
  elementInfo: {
45
44
  tagName: element.tagName,
46
- classes:
47
- (svgElement.className as unknown as SVGAnimatedString)?.baseVal ||
48
- element.className ||
49
- "",
45
+ classes: readElementClasses(element),
50
46
  visualSelectorId: host.getSelectedElementId(),
51
47
  content: newContent,
52
48
  dataSourceLocation: element.dataset.sourceLocation,
package/src/index.ts CHANGED
@@ -8,6 +8,19 @@ import { buildStatusPlugin } from "./build-status-plugin.js";
8
8
 
9
9
  const isRunningInSandbox = !!process.env.MODAL_SANDBOX_ID;
10
10
 
11
+ const ENV_UNSET_GUIDANCE = [
12
+ "[base44] No Base44 backend configured — VITE_BASE44_APP_BASE_URL is not set, so /api requests have nowhere to go.",
13
+ "[base44] Run the app through the Base44 CLI instead:",
14
+ "[base44] base44 dev app + local backend (throwaway local data)",
15
+ "[base44] base44 dev --remote app + your app's real backend (production data)",
16
+ ].join("\n");
17
+
18
+ const BUILD_WITHOUT_APP_ID_WARNING = [
19
+ "[base44] Warning: VITE_BASE44_APP_ID is not set — this build",
20
+ "[base44] will not know its app id and its API calls will fail.",
21
+ "[base44] Build with: base44 build (or base44 deploy --build)",
22
+ ].join("\n");
23
+
11
24
  export default function vitePlugin(
12
25
  opts: {
13
26
  legacySDKImports?: boolean;
@@ -28,9 +41,13 @@ export default function vitePlugin(
28
41
  return [
29
42
  {
30
43
  name: "base44",
31
- config: ({ mode, root = process.cwd() }) => {
44
+ config: ({ mode, root = process.cwd() }, { command }) => {
32
45
  const env = loadEnv(mode ?? "development", root, "");
33
46
 
47
+ if (!isRunningInSandbox && command === "build" && !env.VITE_BASE44_APP_ID) {
48
+ console.log(BUILD_WITHOUT_APP_ID_WARNING);
49
+ }
50
+
34
51
  return {
35
52
  resolve: {
36
53
  alias: {
@@ -110,7 +127,9 @@ export default function vitePlugin(
110
127
  };
111
128
  }
112
129
  console.log(
113
- "[base44] Proxy not enabled (VITE_BASE44_APP_BASE_URL not set)"
130
+ command === "serve"
131
+ ? ENV_UNSET_GUIDANCE
132
+ : "[base44] Proxy not enabled (VITE_BASE44_APP_BASE_URL not set)"
114
133
  );
115
134
  return {};
116
135
  })()),
@@ -56,7 +56,7 @@ function onAppError({
56
56
  }: {
57
57
  title: string;
58
58
  details: string;
59
- componentName: string;
59
+ componentName: string | undefined;
60
60
  originalError: any;
61
61
  }) {
62
62
  if (originalError?.response?.status === 402) {
@@ -80,35 +80,52 @@ function onAppError({
80
80
  );
81
81
  }
82
82
 
83
- function handleUnhandledRejection(event: any) {
84
- const stack = event.reason.stack;
85
- // extract function name from "at X (eval" where x is the function name
86
- const functionName = stack.match(/at\s+(\w+)\s+\(eval/)?.[1];
87
- const msg = functionName
88
- ? `Error in ${functionName}: ${event.reason.toString()}`
89
- : event.reason.toString();
83
+ // extract function name from "at X (eval" where X is the function name
84
+ function extractFunctionName(stack: unknown): string | undefined {
85
+ if (typeof stack !== "string") return undefined;
86
+ return stack.match(/at\s+(\w+)\s+\(eval/)?.[1];
87
+ }
88
+
89
+ // A rejection reason or error can be any value — undefined, a string, a
90
+ // null-prototype object. Throwing here masks the real error entirely.
91
+ function describeErrorValue(value: unknown): string {
92
+ try {
93
+ return String(value);
94
+ } catch {
95
+ return Object.prototype.toString.call(value);
96
+ }
97
+ }
98
+
99
+ export function handleUnhandledRejection(event: any) {
100
+ const reason = event?.reason;
101
+ const functionName = extractFunctionName(reason?.stack);
102
+ const text = describeErrorValue(reason);
103
+ const msg = functionName ? `Error in ${functionName}: ${text}` : text;
90
104
  onAppError({
91
105
  title: msg,
92
- details: event.reason.toString(),
106
+ details: text,
93
107
  componentName: functionName,
94
- originalError: event.reason,
108
+ originalError: reason,
95
109
  });
96
110
  }
97
111
 
98
- function handleWindowError(event: any) {
99
- const stack = event.error?.stack;
100
- let functionName = stack.match(/at\s+(\w+)\s+\(eval/)?.[1];
112
+ export function handleWindowError(event: any) {
113
+ const error = event?.error;
114
+ let functionName = extractFunctionName(error?.stack);
101
115
  if (functionName === "eval") {
102
- functionName = null;
116
+ functionName = undefined;
103
117
  }
104
118
 
105
- const msg = functionName
106
- ? `in ${functionName}: ${event.error.toString()}`
107
- : event.error.toString();
119
+ // window.onerror fires with a null error for cross-origin script failures
120
+ const text =
121
+ error == null && typeof event?.message === "string"
122
+ ? event.message
123
+ : describeErrorValue(error);
124
+ const msg = functionName ? `in ${functionName}: ${text}` : text;
108
125
  onAppError({
109
126
  title: msg,
110
- details: event.error.toString(),
127
+ details: text,
111
128
  componentName: functionName,
112
- originalError: event.error,
129
+ originalError: error,
113
130
  });
114
131
  }
@@ -98,6 +98,15 @@ export function updateElementAttribute(elements: Element[], attribute: string, v
98
98
  });
99
99
  }
100
100
 
101
+ /**
102
+ * Read an element's classes as a plain string, safe to postMessage.
103
+ * `element.className` is an SVGAnimatedString on SVG elements, which is not
104
+ * structured-cloneable — posting it throws DataCloneError and the preview hangs.
105
+ */
106
+ export function readElementClasses(element: Element): string {
107
+ return element.getAttribute("class") ?? "";
108
+ }
109
+
101
110
  /** Collect attribute values from an element for a given allowlist. */
102
111
  export function collectAllowedAttributes(element: Element, allowedAttributes: string[]): Record<string, string> {
103
112
  const attributes: Record<string, string> = {};
@@ -1,4 +1,4 @@
1
- import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES, getElementSelectorId, stopAnimations, resumeAnimations, findInstrumentedElement, resolveHoverTarget, positionLabel, injectFontFaceCss } from "./utils.js";
1
+ import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, readElementClasses, ALLOWED_ATTRIBUTES, getElementSelectorId, stopAnimations, resumeAnimations, findInstrumentedElement, resolveHoverTarget, positionLabel, injectFontFaceCss } from "./utils.js";
2
2
  import { createLayerController } from "./layer-dropdown/controller.js";
3
3
  import { LAYER_DROPDOWN_ATTR } from "./layer-dropdown/consts.js";
4
4
  import { createInlineEditController } from "../capabilities/inline-edit/index.js";
@@ -137,7 +137,6 @@ export function setupVisualEditAgent() {
137
137
  const notifyElementSelected = (element: Element) => {
138
138
  const htmlElement = element as HTMLElement;
139
139
  const rect = element.getBoundingClientRect();
140
- const svgElement = element as SVGElement;
141
140
  const isTextElement = TEXT_TAGS.includes(element.tagName?.toLowerCase());
142
141
 
143
142
  const arrEl = htmlElement.closest("[data-arr-variable-name]") as HTMLElement | null;
@@ -153,10 +152,7 @@ export function setupVisualEditAgent() {
153
152
  window.parent.postMessage({
154
153
  type: "element-selected",
155
154
  tagName: element.tagName,
156
- classes:
157
- (svgElement.className as unknown as SVGAnimatedString)?.baseVal ||
158
- element.className ||
159
- "",
155
+ classes: readElementClasses(element),
160
156
  visualSelectorId: getElementSelectorId(element),
161
157
  content: isTextElement ? htmlElement.innerText : undefined,
162
158
  dataSourceLocation: htmlElement.dataset.sourceLocation,
@@ -382,6 +382,18 @@ export class DataItemFieldProcessor {
382
382
  }
383
383
 
384
384
  const fieldToInject = idField ?? "id";
385
+
386
+ // Never inject a param whose name is already bound where the JSX
387
+ // element sits. A function-scope binding (e.g. `const { id } =
388
+ // useParams()`) makes the output fail to compile — "The symbol 'id'
389
+ // has already been declared" — an outer-scope binding would be
390
+ // silently shadowed by an undefined prop, and a block-scope binding
391
+ // between the element and the function would make the emitted
392
+ // attribute read the wrong value. Anchoring on the element's scope
393
+ // chain covers all three; skipping just degrades visual-edit tracing
394
+ // for this element.
395
+ if (path.scope.hasBinding(fieldToInject)) return;
396
+
385
397
  const newProp = this.types.objectProperty(
386
398
  this.types.identifier(fieldToInject),
387
399
  this.types.identifier(fieldToInject),
@@ -14,7 +14,7 @@ It adds two HTML `data-*` attributes to every JSX element:
14
14
 
15
15
  // AFTER (served to browser):
16
16
  <Button
17
- data-source-location="components/Button:42:8"
17
+ data-source-location="src/components/Button.jsx:42:8"
18
18
  data-dynamic-content="false"
19
19
  className="px-4"
20
20
  >Click me</Button>
@@ -29,7 +29,7 @@ It adds two HTML `data-*` attributes to every JSX element:
29
29
  | Condition | Where | Why |
30
30
  |-----------|-------|-----|
31
31
  | `MODAL_SANDBOX_ID` env var is set | `index.ts:8` | Only runs inside Modal sandbox containers |
32
- | Vite mode is `"development"` | `apply: (config) => config.mode === "development"` | No overhead in production builds |
32
+ | Dev **server** in `"development"` mode | `apply: (config, env) => env.command === "serve" && config.mode === "development"` | The injected `data-*` markers are only consumed by the visual-edit agent in the live preview, which the dev server serves. Builds never use them — including `vite build --mode development` (the platform's validation build), where instrumenting user code once produced compile errors on code that wasn't on disk |
33
33
  | File is `.js`, `.jsx`, `.ts`, or `.tsx` | `id.match(/\.(jsx?\|tsx?)$/)` | Only JSX-capable files |
34
34
  | File is NOT in `node_modules` | `id.includes("node_modules")` | Skip third-party code |
35
35
  | File is NOT `visual-edit-agent` | `id.includes("visual-edit-agent")` | Don't instrument the agent itself |
@@ -37,7 +37,8 @@ It adds two HTML `data-*` attributes to every JSX element:
37
37
  ### Execution Timeline
38
38
 
39
39
  ```
40
- 1. npm run dev
40
+ 1. npm run dev (dev SERVER only — `vite build` never registers this plugin,
41
+ regardless of mode)
41
42
  2. Vite starts, loads plugin config
42
43
  3. index.ts checks: isRunningInSandbox = !!process.env.MODAL_SANDBOX_ID
43
44
  4. If true → registers visualEditPlugin() in the plugin pipeline
@@ -65,19 +66,18 @@ It adds two HTML `data-*` attributes to every JSX element:
65
66
 
66
67
  ### Step 1: Filename Extraction (lines 123-173)
67
68
 
68
- Builds a human-readable source path from the full file path:
69
+ Builds the canonical real source path (the path the backend and sandbox use) from the full file path:
69
70
 
70
71
  ```
71
- /Users/dev/project/src/pages/About/index.tsx → "pages/About/index"
72
- /Users/dev/project/src/components/ui/Button.tsx → "components/ui/Button"
73
- /Users/dev/project/src/Layout.tsx → "Layout"
72
+ /Users/dev/project/src/pages/About/index.tsx → "src/pages/About/index.tsx"
73
+ /Users/dev/project/src/components/ui/Button.tsx → "src/components/ui/Button.tsx"
74
+ /Users/dev/project/src/Layout.tsx → "src/Layout.tsx"
74
75
  ```
75
76
 
76
77
  **Rules:**
77
- - Files under `/pages/` preserves `pages/...` prefix with nested structure
78
- - Files under `/components/` preserves `components/...` prefix with nested structure
79
- - All other files → just the filename (no directory prefix)
80
- - File extensions are always stripped
78
+ - Anchors on the last `src/` segment and keeps everything from there, preserving nested structure
79
+ - The file extension is retained (`.jsx`/`.tsx`/`.js`/`.ts`)
80
+ - Files not under `src/` → just the filename (extension kept)
81
81
 
82
82
  ### Step 2: AST Parsing (lines 177-196)
83
83
 
@@ -260,7 +260,7 @@ The parent window (editor UI) and the sandbox iframe can't share a DOM. The `dat
260
260
  ```
261
261
  Parent Window (Editor UI) iframe Sandbox (React App)
262
262
  ───────────────────────── ──────────────────────────
263
- "Select element components/Button:42:8" → querySelector('[data-source-location="components/Button:42:8"]')
263
+ "Select element src/components/Button.jsx:42:8" → querySelector('[data-source-location="src/components/Button.jsx:42:8"]')
264
264
  ← "Element found: classes='px-4', isDynamic=false"
265
265
  "Update classes to 'px-6 py-3'" → element.setAttribute("class", "px-6 py-3")
266
266
  ```
@@ -305,7 +305,7 @@ The `transformIndexHtml` hook injects the Tailwind CSS CDN, enabling visual edit
305
305
  │ │ React App (DOM) │ │
306
306
  │ │ │ │
307
307
  │ │ <div data-source-location= │ │
308
- │ │ "pages/Home:10:4" │ │
308
+ │ │ "src/pages/Home.jsx:10:4" │ │
309
309
  │ │ data-dynamic-content="true"> │ │
310
310
  │ │ {greeting} │ │
311
311
  │ │ </div> │ │
@@ -2,14 +2,21 @@ import { parse } from "@babel/parser";
2
2
  import { default as traverse } from "@babel/traverse";
3
3
  import { default as generate } from "@babel/generator";
4
4
  import * as t from "@babel/types";
5
- import type { Plugin } from "vite";
5
+ import type { ConfigEnv, Plugin, UserConfig } from "vite";
6
6
  import { JSXProcessor } from "./jsx-processor.js";
7
7
  import { JSXUtils } from "./jsx-utils.js";
8
8
 
9
9
  export function visualEditPlugin() {
10
10
  return {
11
11
  name: "visual-edit-transform",
12
- apply: (config) => config.mode === "development",
12
+ // Dev SERVER only: the injected data-* markers are consumed by the
13
+ // visual-edit agent in the live preview, which vite serves — never by
14
+ // build output. Gating on mode alone also caught `vite build --mode
15
+ // development` (the platform's validation build), where instrumenting
16
+ // user code is pure risk: an injection bug there fails the build on
17
+ // code that doesn't exist on disk.
18
+ apply: (config: UserConfig, env: ConfigEnv) =>
19
+ env.command === "serve" && config.mode === "development",
13
20
  enforce: "pre",
14
21
  order: "pre",
15
22
  transformIndexHtml(html: any) {
@@ -81,30 +88,19 @@ export function visualEditPlugin() {
81
88
  } as Plugin;
82
89
  }
83
90
 
91
+ // Emit the canonical real path (src/pages/Home.jsx) the backend and sandbox use, NOT a
92
+ // legacy alias (pages/Home). The path is anchored on the last "src" segment with the
93
+ // extension retained, so consumers get the file's true sandbox path directly.
84
94
  function extractFilename(id: string): string {
85
- const pathParts = id.split("/");
86
-
87
- const segmentIndex = findSegmentIndex(pathParts, ["pages", "components"]);
88
- if (segmentIndex >= 0 && segmentIndex < pathParts.length - 1) {
89
- const relevantParts = pathParts.slice(segmentIndex);
90
- const last = relevantParts[relevantParts.length - 1];
91
- relevantParts[relevantParts.length - 1] = stripExtension(last ?? "");
92
- return relevantParts.join("/");
95
+ // Drop any Vite query suffix (e.g. "?v=...") and normalize separators.
96
+ const cleanId = id.split("?")[0]!.replace(/\\/g, "/");
97
+ const parts = cleanId.split("/");
98
+ // Anchor on the LAST "src" segment so a "src" elsewhere in the absolute path
99
+ // (e.g. a user's home dir) can't mis-anchor. base44 apps keep all source under src/.
100
+ const srcIndex = parts.lastIndexOf("src");
101
+ if (srcIndex >= 0) {
102
+ return parts.slice(srcIndex).join("/"); // src/pages/Home.jsx, src/Layout.jsx, src/components/ui/Card.tsx
93
103
  }
94
-
95
- const lastPart = pathParts[pathParts.length - 1] ?? "";
96
- return stripExtension(lastPart);
97
- }
98
-
99
- function findSegmentIndex(parts: string[], segments: string[]): number {
100
- for (const segment of segments) {
101
- const idx = parts.findIndex((part) => part === segment);
102
- if (idx >= 0) return idx;
103
- }
104
- return -1;
105
- }
106
-
107
- function stripExtension(filename: string): string {
108
- const dotIndex = filename.indexOf(".");
109
- return dotIndex >= 0 ? filename.substring(0, dotIndex) : filename;
104
+ // Fallback: file not under src/ — use the bare filename (extension kept).
105
+ return parts[parts.length - 1] ?? "";
110
106
  }