@hyperframes/studio 0.8.80 → 0.8.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio",
3
- "version": "0.8.80",
3
+ "version": "0.8.81",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -52,12 +52,12 @@
52
52
  "dockview-react": "8.3.1",
53
53
  "gsap": "^3.13.0",
54
54
  "mediabunny": "^1.45.3",
55
- "@hyperframes/core": "0.8.80",
56
- "@hyperframes/parsers": "0.8.80",
57
- "@hyperframes/player": "0.8.80",
58
- "@hyperframes/sdk": "0.8.80",
59
- "@hyperframes/shader-transitions": "0.8.80",
60
- "@hyperframes/studio-server": "0.8.80"
55
+ "@hyperframes/core": "0.8.81",
56
+ "@hyperframes/parsers": "0.8.81",
57
+ "@hyperframes/player": "0.8.81",
58
+ "@hyperframes/sdk": "0.8.81",
59
+ "@hyperframes/shader-transitions": "0.8.81",
60
+ "@hyperframes/studio-server": "0.8.81"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@tailwindcss/vite": "^4.3.3",
@@ -73,7 +73,7 @@
73
73
  "vite": "^6.4.2",
74
74
  "vitest": "^4.1.11",
75
75
  "zustand": "^5.0.0",
76
- "@hyperframes/producer": "0.8.80"
76
+ "@hyperframes/producer": "0.8.81"
77
77
  },
78
78
  "peerDependencies": {
79
79
  "react": "19",
package/src/App.tsx CHANGED
@@ -344,8 +344,7 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
344
344
  effectiveTimelineDuration,
345
345
  });
346
346
  const compositionDimensions = useCompositionDimensions(previewIframeRef);
347
- const { lintModal, linting, handleLint, closeLintModal, findingsByFile, hasLintError } =
348
- useLintModal(projectId, refreshKey);
347
+ const lint = useLintModal(projectId, refreshKey);
349
348
  const frameCapture = useFrameCapture({
350
349
  projectId,
351
350
  activeCompPath,
@@ -500,11 +499,11 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
500
499
  onSelectComposition={handleSelectComposition}
501
500
  onAddBlock={handleAddBlock}
502
501
  onPreviewBlock={setBlockPreview}
503
- onLint={handleLint}
504
- linting={linting}
505
- lintFindingCount={lintModal?.length ?? findingsByFile.size}
506
- lintFindingsByFile={findingsByFile}
507
- lintHasError={hasLintError}
502
+ onLint={lint.handleLint}
503
+ linting={lint.linting}
504
+ lintFindingCount={lint.lintFindingCount}
505
+ lintFindingsByFile={lint.findingsByFile}
506
+ lintHasError={lint.hasLintError}
508
507
  onAddAssetToTimeline={handleAddAssetAtPlayhead}
509
508
  onAddCompositionToTimeline={handleAddCompositionAtPlayhead}
510
509
  />
@@ -577,8 +576,8 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
577
576
  <StudioOverlays
578
577
  projectId={projectId}
579
578
  projectDir={fileManager.projectDir}
580
- lintModal={lintModal}
581
- closeLintModal={closeLintModal}
579
+ lintModal={lint.lintModal}
580
+ closeLintModal={lint.closeLintModal}
582
581
  consoleErrors={consoleErrors}
583
582
  clearConsoleErrors={() => setConsoleErrors(null)}
584
583
  domEditSession={domEditSession}
@@ -23,14 +23,10 @@ import { useStudioPlaybackContext, useStudioShellContext } from "../contexts/Stu
23
23
  import { useFileManagerContext } from "../contexts/FileManagerContext";
24
24
  import { useDomEditContext } from "../contexts/DomEditContext";
25
25
  import { usePlayerStore } from "../player";
26
- import {
27
- applyColorGradingScopeUpdate,
28
- EMPTY_COLOR_GRADING_SCOPE_RESULT,
29
- type ColorGradingScope,
30
- } from "./studioColorGradingScope";
31
26
  import { timelineKeysForSelections } from "../utils/studioHelpers";
32
27
  import { canHideSelections } from "../utils/timelineInspector";
33
28
  import { useRemoveBackground } from "../hooks/useRemoveBackground";
29
+ import { useApplyColorGradingScope } from "../hooks/useApplyColorGradingScope";
34
30
 
35
31
  // fallow-ignore-next-line complexity
36
32
  export function StudioRightPanels({
@@ -49,14 +45,8 @@ export function StudioRightPanels({
49
45
  onAutoGroupCarveSources,
50
46
  onAddMediaOverlay,
51
47
  }: StudioRightPanelsProps) {
52
- const {
53
- previewIframeRef,
54
- projectId,
55
- activeCompPath,
56
- showToast,
57
- waitForPendingDomEditSaves,
58
- renderQueue,
59
- } = useStudioShellContext();
48
+ const { previewIframeRef, projectId, activeCompPath, showToast, renderQueue } =
49
+ useStudioShellContext();
60
50
  const { captionEditMode, refreshKey } = useStudioPlaybackContext();
61
51
 
62
52
  const {
@@ -114,7 +104,6 @@ export function StudioRightPanels({
114
104
  refreshFileTree,
115
105
  readProjectFile,
116
106
  writeProjectFile,
117
- fileTree,
118
107
  editingFile,
119
108
  } = useFileManagerContext();
120
109
 
@@ -158,40 +147,7 @@ export function StudioRightPanels({
158
147
  });
159
148
  useCaptionDesignFocus(captionEditMode);
160
149
 
161
- const handleApplyColorGradingScope = useCallback(
162
- async (scope: ColorGradingScope, value: string | null) =>
163
- applyColorGradingScopeUpdate({
164
- scope,
165
- value,
166
- selectedSourceFile: domEditSelection?.sourceFile || activeCompPath || "index.html",
167
- fileTree,
168
- projectId,
169
- waitForPendingDomEditSaves,
170
- readProjectFile,
171
- writeProjectFile,
172
- recordEdit,
173
- reloadPreview,
174
- showToast,
175
- }).catch((error) => {
176
- showToast(
177
- `Couldn't apply color grading: ${error instanceof Error ? error.message : String(error)}`,
178
- "error",
179
- );
180
- return EMPTY_COLOR_GRADING_SCOPE_RESULT;
181
- }),
182
- [
183
- activeCompPath,
184
- domEditSelection?.sourceFile,
185
- fileTree,
186
- projectId,
187
- readProjectFile,
188
- recordEdit,
189
- reloadPreview,
190
- showToast,
191
- waitForPendingDomEditSaves,
192
- writeProjectFile,
193
- ],
194
- );
150
+ const handleApplyColorGradingScope = useApplyColorGradingScope(recordEdit, reloadPreview);
195
151
 
196
152
  const handleRemoveBackground = useRemoveBackground(projectId, refreshFileTree, showToast);
197
153
 
@@ -85,7 +85,7 @@ function CompositionSection({
85
85
  }
86
86
 
87
87
  export function VariablesOtherCompositions({
88
- fileTree,
88
+ compositionPaths,
89
89
  excludePath,
90
90
  refreshKey,
91
91
  readProjectFile,
@@ -93,8 +93,8 @@ export function VariablesOtherCompositions({
93
93
  recordEdit,
94
94
  reloadPreview,
95
95
  }: {
96
- fileTree: string[];
97
- excludePath: string;
96
+ compositionPaths: string[];
97
+ excludePath: string | null;
98
98
  refreshKey: unknown;
99
99
  readProjectFile: (path: string) => Promise<string>;
100
100
  writeProjectFile: (path: string, content: string) => Promise<void>;
@@ -103,7 +103,7 @@ export function VariablesOtherCompositions({
103
103
  }) {
104
104
  const [selfRefresh, setSelfRefresh] = useState(0);
105
105
  const groups = useProjectCompositionVariables(
106
- fileTree,
106
+ compositionPaths,
107
107
  excludePath,
108
108
  readProjectFile,
109
109
  `${refreshKey}:${selfRefresh}`,
@@ -0,0 +1,86 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import React, { act } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { afterEach, describe, expect, it, vi } from "vitest";
6
+ import { openComposition } from "@hyperframes/sdk";
7
+ import { VariablesPanel } from "./VariablesPanel";
8
+
9
+ (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
10
+
11
+ const withTitle = (id: string, label: string) => `<!DOCTYPE html>
12
+ <html data-composition-variables='[{"id":"title","type":"string","label":"${label}","default":"Hello"}]'>
13
+ <body><div data-composition-id="${id}" data-hf-id="hf-stage" data-hf-root data-duration="5"></div></body>
14
+ </html>`;
15
+
16
+ const files: Record<string, string> = {
17
+ "index.html":
18
+ '<html><body><div data-hf-id="hf-stage" data-hf-root data-duration="5"></div></body></html>',
19
+ "main.html": withTitle("main", "Main title"),
20
+ "scene.html": withTitle("scene", "Scene title"),
21
+ ".hyperframes/preview/4884df6e.html": withTitle("root", "Generated title"),
22
+ };
23
+ const shell = {
24
+ activeCompPath: "index.html" as string | null,
25
+ compositions: ["index.html", "scene.html"],
26
+ };
27
+
28
+ vi.mock("../../contexts/StudioContext", () => ({
29
+ useStudioShellContext: () => ({ activeCompPath: shell.activeCompPath, showToast: vi.fn() }),
30
+ useStudioPlaybackContext: () => ({ refreshKey: 0 }),
31
+ }));
32
+ vi.mock("../../contexts/DomEditContext", () => ({
33
+ useDomEditContext: () => ({ domEditSelection: null }),
34
+ }));
35
+ vi.mock("../../contexts/FileManagerContext", () => ({
36
+ useFileManagerContext: () => ({
37
+ readProjectFile: async (path: string) => files[path] ?? "",
38
+ writeProjectFile: vi.fn(),
39
+ fileTree: Object.keys(files),
40
+ compositions: shell.compositions,
41
+ }),
42
+ }));
43
+
44
+ let root: Root | null = null;
45
+
46
+ afterEach(() => {
47
+ act(() => root?.unmount());
48
+ root = null;
49
+ document.body.innerHTML = "";
50
+ });
51
+
52
+ async function renderOtherCompositions(sessionPath: string): Promise<string> {
53
+ const host = document.createElement("div");
54
+ document.body.append(host);
55
+ root = createRoot(host);
56
+ const session = await openComposition(files[sessionPath]!, { history: false });
57
+ await act(async () => {
58
+ root?.render(
59
+ <VariablesPanel
60
+ sdkSession={session}
61
+ publishSdkSession={vi.fn()}
62
+ reloadPreview={vi.fn()}
63
+ recordEdit={vi.fn(async () => {})}
64
+ />,
65
+ );
66
+ });
67
+ const other = () =>
68
+ [...host.querySelectorAll("p")].find((p) => p.textContent === "Other compositions")
69
+ ?.parentElement?.textContent ?? "";
70
+ await vi.waitFor(() => expect(other()).toContain("scene.html"));
71
+ return other();
72
+ }
73
+
74
+ describe("VariablesPanel other compositions", () => {
75
+ it("lists the project's compositions, not Studio's generated preview documents", async () => {
76
+ shell.activeCompPath = "index.html";
77
+ shell.compositions = ["index.html", "scene.html"];
78
+ expect(await renderOtherCompositions("index.html")).not.toContain(".hyperframes/preview");
79
+ });
80
+
81
+ it("leaves out the main composition in the master view when it is not index.html", async () => {
82
+ shell.activeCompPath = null;
83
+ shell.compositions = ["main.html", "scene.html"];
84
+ expect(await renderOtherCompositions("main.html")).not.toContain("main.html");
85
+ });
86
+ });
@@ -254,7 +254,7 @@ export const VariablesPanel = memo(function VariablesPanel({
254
254
  }: VariablesPanelProps) {
255
255
  const { activeCompPath, showToast } = useStudioShellContext();
256
256
  const { refreshKey } = useStudioPlaybackContext();
257
- const { readProjectFile, writeProjectFile, fileTree, compositions } = useFileManagerContext();
257
+ const { readProjectFile, writeProjectFile, compositions } = useFileManagerContext();
258
258
  const { domEditSelection } = useDomEditContext();
259
259
  // Master view (no activeCompPath) targets the real main composition, not a
260
260
  // hardcoded index.html — used for both the persist write target and the
@@ -537,8 +537,8 @@ export const VariablesPanel = memo(function VariablesPanel({
537
537
  </button>
538
538
  )}
539
539
  <VariablesOtherCompositions
540
- fileTree={fileTree}
541
- excludePath={activeCompPath ?? "index.html"}
540
+ compositionPaths={compositions}
541
+ excludePath={effectiveCompPath}
542
542
  refreshKey={`${refreshKey}:${revision}`}
543
543
  readProjectFile={readProjectFile}
544
544
  writeProjectFile={writeProjectFile}
@@ -44,6 +44,35 @@ describe("SidebarLintButton badge", () => {
44
44
  expect(badge?.textContent).toContain("including errors");
45
45
  });
46
46
 
47
+ it("pulses a few times and then holds still, so an idle Studio paints nothing", () => {
48
+ const badge = renderBadge(true);
49
+ expect(badge?.style.animationIterationCount).toBe("3");
50
+ });
51
+
52
+ it("pulses again when the finding count changes", () => {
53
+ const host = document.createElement("div");
54
+ document.body.append(host);
55
+ root = createRoot(host);
56
+ const render = (findingCount: number) =>
57
+ act(() => {
58
+ root?.render(
59
+ <SidebarLintButton
60
+ onLint={vi.fn()}
61
+ linting={false}
62
+ findingCount={findingCount}
63
+ hasError
64
+ />,
65
+ );
66
+ });
67
+ render(2);
68
+ const first = host.querySelector("[data-lint-badge]");
69
+ render(2);
70
+ expect(host.querySelector("[data-lint-badge]")).toBe(first);
71
+ render(3);
72
+ // A new element restarts its CSS animation; a text change on the old one would not.
73
+ expect(host.querySelector("[data-lint-badge]")).not.toBe(first);
74
+ });
75
+
47
76
  it("stays still when the findings are warnings only", () => {
48
77
  const badge = renderBadge(false);
49
78
  expect(badge?.dataset.lintBadge).toBe("warning");
@@ -1,3 +1,5 @@
1
+ const ERROR_PULSES_BEFORE_IDLE = 3;
2
+
1
3
  export function SidebarLintButton({
2
4
  onLint,
3
5
  linting,
@@ -31,7 +33,9 @@ export function SidebarLintButton({
31
33
  {linting ? "Linting…" : "Lint"}
32
34
  {!linting && findingCount != null && findingCount > 0 && (
33
35
  <span
36
+ key={findingCount}
34
37
  data-lint-badge={hasError ? "error" : "warning"}
38
+ style={hasError ? { animationIterationCount: ERROR_PULSES_BEFORE_IDLE } : undefined}
35
39
  className={
36
40
  hasError
37
41
  ? "ml-1 min-w-[16px] rounded-full bg-panel-danger/25 px-1 text-[9px] font-bold text-panel-danger animate-pulse motion-reduce:animate-none"
@@ -22,7 +22,7 @@ interface ApplyColorGradingScopeOptions {
22
22
  scope: ColorGradingScope;
23
23
  value: string | null;
24
24
  selectedSourceFile: string;
25
- fileTree: string[];
25
+ compositionPaths: string[];
26
26
  projectId: string;
27
27
  waitForPendingDomEditSaves: () => Promise<void>;
28
28
  readProjectFile: ProjectFileReader;
@@ -35,11 +35,9 @@ interface ApplyColorGradingScopeOptions {
35
35
  function colorGradingScopePaths(
36
36
  scope: ColorGradingScope,
37
37
  selectedSourceFile: string,
38
- fileTree: string[],
38
+ compositionPaths: string[],
39
39
  ): string[] {
40
- return scope === "source-file"
41
- ? [selectedSourceFile]
42
- : fileTree.filter((path) => /\.html?$/i.test(path));
40
+ return scope === "source-file" ? [selectedSourceFile] : compositionPaths;
43
41
  }
44
42
 
45
43
  async function patchColorGradingScopeFiles(
@@ -72,7 +70,7 @@ export async function applyColorGradingScopeUpdate({
72
70
  scope,
73
71
  value,
74
72
  selectedSourceFile,
75
- fileTree,
73
+ compositionPaths,
76
74
  projectId,
77
75
  waitForPendingDomEditSaves,
78
76
  readProjectFile,
@@ -91,7 +89,7 @@ export async function applyColorGradingScopeUpdate({
91
89
  }
92
90
 
93
91
  const { files, changedElements } = await patchColorGradingScopeFiles(
94
- colorGradingScopePaths(scope, selectedSourceFile, fileTree),
92
+ colorGradingScopePaths(scope, selectedSourceFile, compositionPaths),
95
93
  value,
96
94
  readProjectFile,
97
95
  );
@@ -0,0 +1,53 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import React, { act } from "react";
4
+ import { createRoot } from "react-dom/client";
5
+ import { expect, it, vi } from "vitest";
6
+ import { useApplyColorGradingScope } from "./useApplyColorGradingScope";
7
+
8
+ (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
9
+
10
+ const readProjectFile = vi.fn(async (_path: string) => "<html><body></body></html>");
11
+
12
+ vi.mock("../contexts/StudioContext", () => ({
13
+ useStudioShellContext: () => ({
14
+ projectId: "demo",
15
+ activeCompPath: "index.html",
16
+ showToast: vi.fn(),
17
+ waitForPendingDomEditSaves: async () => {},
18
+ }),
19
+ }));
20
+ vi.mock("../contexts/DomEditContext", () => ({
21
+ useDomEditContext: () => ({ domEditSelection: null }),
22
+ }));
23
+ vi.mock("../contexts/FileManagerContext", () => ({
24
+ useFileManagerContext: () => ({
25
+ readProjectFile,
26
+ writeProjectFile: vi.fn(),
27
+ fileTree: ["index.html", "scene.html", ".hyperframes/preview/4884df6e.html"],
28
+ compositions: ["index.html", "scene.html"],
29
+ }),
30
+ }));
31
+
32
+ it("grades the project's compositions, not Studio's generated preview documents", async () => {
33
+ let apply: ReturnType<typeof useApplyColorGradingScope> | null = null;
34
+ function Probe() {
35
+ apply = useApplyColorGradingScope(
36
+ vi.fn(async () => {}),
37
+ vi.fn(),
38
+ );
39
+ return null;
40
+ }
41
+ const root = createRoot(document.createElement("div"));
42
+ act(() => root.render(<Probe />));
43
+
44
+ await act(async () => {
45
+ await apply!("project", null);
46
+ });
47
+
48
+ expect(readProjectFile.mock.calls.map(([path]) => path).sort()).toEqual([
49
+ "index.html",
50
+ "scene.html",
51
+ ]);
52
+ act(() => root.unmount());
53
+ });
@@ -0,0 +1,54 @@
1
+ import { useCallback } from "react";
2
+ import { useStudioShellContext } from "../contexts/StudioContext";
3
+ import { useDomEditContext } from "../contexts/DomEditContext";
4
+ import { useFileManagerContext } from "../contexts/FileManagerContext";
5
+ import {
6
+ applyColorGradingScopeUpdate,
7
+ EMPTY_COLOR_GRADING_SCOPE_RESULT,
8
+ type ColorGradingScope,
9
+ } from "../components/studioColorGradingScope";
10
+ import type { StudioRightPanelsProps } from "../components/StudioRightPanels.types";
11
+
12
+ export function useApplyColorGradingScope(
13
+ recordEdit: StudioRightPanelsProps["recordEdit"],
14
+ reloadPreview: StudioRightPanelsProps["reloadPreview"],
15
+ ) {
16
+ const { projectId, activeCompPath, showToast, waitForPendingDomEditSaves } =
17
+ useStudioShellContext();
18
+ const { domEditSelection } = useDomEditContext();
19
+ const { readProjectFile, writeProjectFile, compositions } = useFileManagerContext();
20
+ return useCallback(
21
+ async (scope: ColorGradingScope, value: string | null) =>
22
+ applyColorGradingScopeUpdate({
23
+ scope,
24
+ value,
25
+ selectedSourceFile: domEditSelection?.sourceFile || activeCompPath || "index.html",
26
+ compositionPaths: compositions,
27
+ projectId,
28
+ waitForPendingDomEditSaves,
29
+ readProjectFile,
30
+ writeProjectFile,
31
+ recordEdit,
32
+ reloadPreview,
33
+ showToast,
34
+ }).catch((error) => {
35
+ showToast(
36
+ `Couldn't apply color grading: ${error instanceof Error ? error.message : String(error)}`,
37
+ "error",
38
+ );
39
+ return EMPTY_COLOR_GRADING_SCOPE_RESULT;
40
+ }),
41
+ [
42
+ activeCompPath,
43
+ compositions,
44
+ domEditSelection?.sourceFile,
45
+ projectId,
46
+ readProjectFile,
47
+ recordEdit,
48
+ reloadPreview,
49
+ showToast,
50
+ waitForPendingDomEditSaves,
51
+ writeProjectFile,
52
+ ],
53
+ );
54
+ }
@@ -69,6 +69,21 @@ describe("useLintModal hasLintError", () => {
69
69
  });
70
70
  });
71
71
 
72
+ describe("useLintModal lintFindingCount", () => {
73
+ it("counts findings, not files, with the result open and after it closes", async () => {
74
+ const findings = ["a", "b", "c"].map((message) => ({
75
+ severity: "warning",
76
+ message,
77
+ file: "index.html",
78
+ }));
79
+ await mountWithLint(async () => ({ json: async () => ({ findings }) }));
80
+ expect(latest?.lintFindingCount).toBe(3);
81
+ act(() => latest?.closeLintModal());
82
+ expect(latest?.findingsByFile.size).toBe(1);
83
+ expect(latest?.lintFindingCount).toBe(3);
84
+ });
85
+ });
86
+
72
87
  describe("useLintModal background lint", () => {
73
88
  it("waits for the live preview to boot before asking the server to lint", async () => {
74
89
  usePlayerStore.setState({ previewBooted: false });
@@ -98,8 +98,10 @@ export function useLintModal(projectId: string | null, refreshKey?: number) {
98
98
 
99
99
  const findingsByElement = useMemo(() => groupFindings((f) => f.elementId), [groupFindings]);
100
100
  const findingsByFile = useMemo(() => groupFindings((f) => f.file), [groupFindings]);
101
- // Reads the list the badge count reads: the manual result when present, else the background one.
102
- const hasLintError = (lintModal ?? backgroundFindings).some((f) => f.severity === "error");
101
+ // The badge counts findings, not files: the manual result when present, else the background one.
102
+ const badgeFindings = lintModal ?? backgroundFindings;
103
+ const lintFindingCount = badgeFindings.length;
104
+ const hasLintError = badgeFindings.some((f) => f.severity === "error");
103
105
 
104
106
  // Sync lint findings directly to the player store — eliminates the
105
107
  // mirroring useEffect that was previously in App.tsx.
@@ -115,6 +117,7 @@ export function useLintModal(projectId: string | null, refreshKey?: number) {
115
117
  backgroundFindings,
116
118
  findingsByElement,
117
119
  findingsByFile,
120
+ lintFindingCount,
118
121
  hasLintError,
119
122
  };
120
123
  }
@@ -51,7 +51,7 @@ async function readGroup(
51
51
  * full parse, so large projects don't pay N openComposition calls.
52
52
  */
53
53
  export function useProjectCompositionVariables(
54
- fileTree: string[],
54
+ compositionPaths: string[],
55
55
  excludePath: string | null,
56
56
  readProjectFile: (path: string) => Promise<string>,
57
57
  refreshKey: unknown,
@@ -60,7 +60,7 @@ export function useProjectCompositionVariables(
60
60
 
61
61
  useEffect(() => {
62
62
  let cancelled = false;
63
- const htmlFiles = fileTree.filter((p) => p.endsWith(".html") && p !== excludePath);
63
+ const htmlFiles = compositionPaths.filter((p) => p !== excludePath);
64
64
 
65
65
  void (async () => {
66
66
  const out: CompositionVariableGroup[] = [];
@@ -75,7 +75,7 @@ export function useProjectCompositionVariables(
75
75
  cancelled = true;
76
76
  };
77
77
  // eslint-disable-next-line react-hooks/exhaustive-deps
78
- }, [fileTree, excludePath, readProjectFile, refreshKey]);
78
+ }, [compositionPaths, excludePath, readProjectFile, refreshKey]);
79
79
 
80
80
  return groups;
81
81
  }