@tscircuit/cli 0.1.2383 → 0.1.2384

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.
@@ -31934,11 +31934,28 @@ var kicadPcmSettingsSchema = z.object({
31934
31934
  schemaVersion: z.union([z.literal("auto"), z.literal(1), z.literal(2)]).optional(),
31935
31935
  license: z.string().trim().min(1).optional()
31936
31936
  });
31937
+ var extraRunFrameTabSchema = z.object({
31938
+ tabName: z.string().trim().min(1),
31939
+ iframeUrl: z.string().url().refine((url) => /^https?:\/\//i.test(url), {
31940
+ message: "Extra RunFrame tabs require an HTTP or HTTPS URL"
31941
+ }).refine((url) => {
31942
+ try {
31943
+ const parsed = new URL(url);
31944
+ return !parsed.username && !parsed.password;
31945
+ } catch {
31946
+ return false;
31947
+ }
31948
+ }, {
31949
+ message: "Extra RunFrame tab URLs must not include credentials"
31950
+ }),
31951
+ mainComponentPath: z.string().trim().min(1).optional()
31952
+ });
31937
31953
  var projectConfigSchema = z.object({
31938
31954
  mainEntrypoint: z.string().optional(),
31939
31955
  libraryEntrypoint: z.string().optional(),
31940
31956
  previewComponentPath: z.string().optional(),
31941
31957
  siteDefaultComponentPath: z.string().optional(),
31958
+ extraRunFrameTabs: z.array(extraRunFrameTabSchema).optional(),
31942
31959
  ignoredFiles: z.array(z.string()).optional(),
31943
31960
  includeBoardFiles: z.array(z.string()).optional(),
31944
31961
  snapshotsDir: z.string().optional(),
@@ -32021,7 +32038,7 @@ var loadProjectEnv = (projectDir) => {
32021
32038
  }
32022
32039
  }
32023
32040
  };
32024
- var loadProjectConfigSync = (projectDir = process.cwd()) => {
32041
+ var loadProjectConfigSync = (projectDir = process.cwd(), options = {}) => {
32025
32042
  const configPath = path.join(projectDir, CONFIG_FILENAME);
32026
32043
  if (!fs.existsSync(configPath)) {
32027
32044
  return null;
@@ -32031,11 +32048,13 @@ var loadProjectConfigSync = (projectDir = process.cwd()) => {
32031
32048
  const parsedConfig = JSON.parse(configContent);
32032
32049
  return projectConfigSchema.parse(parsedConfig);
32033
32050
  } catch (error) {
32051
+ if (options.throwOnError)
32052
+ throw error;
32034
32053
  console.error(`Error loading tscircuit config: ${error}`);
32035
32054
  return null;
32036
32055
  }
32037
32056
  };
32038
- var loadProjectConfigModule = async (projectDir) => {
32057
+ var loadProjectConfigModule = async (projectDir, options = {}) => {
32039
32058
  loadProjectEnv(projectDir);
32040
32059
  for (const configFileName of CONFIG_MODULE_FILENAMES) {
32041
32060
  const configPath = path.join(projectDir, configFileName);
@@ -32049,15 +32068,17 @@ var loadProjectConfigModule = async (projectDir) => {
32049
32068
  const exportedConfig = importedModule.default ?? importedModule.config ?? importedModule;
32050
32069
  return parseProjectConfigObject(exportedConfig);
32051
32070
  } catch (error) {
32071
+ if (options.throwOnError)
32072
+ throw error;
32052
32073
  console.error(`Error loading ${configFileName}: ${error}`);
32053
32074
  return null;
32054
32075
  }
32055
32076
  }
32056
32077
  return null;
32057
32078
  };
32058
- var loadRuntimeProjectConfig = async (projectDir = process.cwd()) => {
32059
- const jsonConfig = loadProjectConfigSync(projectDir);
32060
- const moduleConfig = await loadProjectConfigModule(projectDir);
32079
+ var loadRuntimeProjectConfig = async (projectDir = process.cwd(), options = {}) => {
32080
+ const jsonConfig = loadProjectConfigSync(projectDir, options);
32081
+ const moduleConfig = await loadProjectConfigModule(projectDir, options);
32061
32082
  if (!jsonConfig && !moduleConfig) {
32062
32083
  return null;
32063
32084
  }
package/dist/cli/main.js CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/cli/main.js
3
- size: 46978861 bytes
4
- sha256: 9726711e7ad7dcde3c882b7a13ad1de482fc5c9450beebf7060851d0b715428c
3
+ size: 46985125 bytes
4
+ sha256: 584e5dd10a58d0cf85334e1312bdd66756b6861e01c1b3db2a917a0ab7d7bba6
@@ -24799,11 +24799,28 @@ var kicadPcmSettingsSchema = z.object({
24799
24799
  schemaVersion: z.union([z.literal("auto"), z.literal(1), z.literal(2)]).optional(),
24800
24800
  license: z.string().trim().min(1).optional()
24801
24801
  });
24802
+ var extraRunFrameTabSchema = z.object({
24803
+ tabName: z.string().trim().min(1),
24804
+ iframeUrl: z.string().url().refine((url) => /^https?:\/\//i.test(url), {
24805
+ message: "Extra RunFrame tabs require an HTTP or HTTPS URL"
24806
+ }).refine((url) => {
24807
+ try {
24808
+ const parsed = new URL(url);
24809
+ return !parsed.username && !parsed.password;
24810
+ } catch {
24811
+ return false;
24812
+ }
24813
+ }, {
24814
+ message: "Extra RunFrame tab URLs must not include credentials"
24815
+ }),
24816
+ mainComponentPath: z.string().trim().min(1).optional()
24817
+ });
24802
24818
  var projectConfigSchema = z.object({
24803
24819
  mainEntrypoint: z.string().optional(),
24804
24820
  libraryEntrypoint: z.string().optional(),
24805
24821
  previewComponentPath: z.string().optional(),
24806
24822
  siteDefaultComponentPath: z.string().optional(),
24823
+ extraRunFrameTabs: z.array(extraRunFrameTabSchema).optional(),
24807
24824
  ignoredFiles: z.array(z.string()).optional(),
24808
24825
  includeBoardFiles: z.array(z.string()).optional(),
24809
24826
  snapshotsDir: z.string().optional(),
@@ -24886,7 +24903,7 @@ var loadProjectEnv = (projectDir) => {
24886
24903
  }
24887
24904
  }
24888
24905
  };
24889
- var loadProjectConfigSync = (projectDir = process.cwd()) => {
24906
+ var loadProjectConfigSync = (projectDir = process.cwd(), options = {}) => {
24890
24907
  const configPath = path10.join(projectDir, CONFIG_FILENAME);
24891
24908
  if (!fs11.existsSync(configPath)) {
24892
24909
  return null;
@@ -24896,11 +24913,13 @@ var loadProjectConfigSync = (projectDir = process.cwd()) => {
24896
24913
  const parsedConfig = JSON.parse(configContent);
24897
24914
  return projectConfigSchema.parse(parsedConfig);
24898
24915
  } catch (error) {
24916
+ if (options.throwOnError)
24917
+ throw error;
24899
24918
  console.error(`Error loading tscircuit config: ${error}`);
24900
24919
  return null;
24901
24920
  }
24902
24921
  };
24903
- var loadProjectConfigModule = async (projectDir) => {
24922
+ var loadProjectConfigModule = async (projectDir, options = {}) => {
24904
24923
  loadProjectEnv(projectDir);
24905
24924
  for (const configFileName of CONFIG_MODULE_FILENAMES) {
24906
24925
  const configPath = path10.join(projectDir, configFileName);
@@ -24914,15 +24933,17 @@ var loadProjectConfigModule = async (projectDir) => {
24914
24933
  const exportedConfig = importedModule.default ?? importedModule.config ?? importedModule;
24915
24934
  return parseProjectConfigObject(exportedConfig);
24916
24935
  } catch (error) {
24936
+ if (options.throwOnError)
24937
+ throw error;
24917
24938
  console.error(`Error loading ${configFileName}: ${error}`);
24918
24939
  return null;
24919
24940
  }
24920
24941
  }
24921
24942
  return null;
24922
24943
  };
24923
- var loadRuntimeProjectConfig = async (projectDir = process.cwd()) => {
24924
- const jsonConfig = loadProjectConfigSync(projectDir);
24925
- const moduleConfig = await loadProjectConfigModule(projectDir);
24944
+ var loadRuntimeProjectConfig = async (projectDir = process.cwd(), options = {}) => {
24945
+ const jsonConfig = loadProjectConfigSync(projectDir, options);
24946
+ const moduleConfig = await loadProjectConfigModule(projectDir, options);
24926
24947
  if (!jsonConfig && !moduleConfig) {
24927
24948
  return null;
24928
24949
  }
package/dist/lib/index.js CHANGED
@@ -71847,8 +71847,16 @@ var getPartsEngineBaseUrls = () => {
71847
71847
  return baseUrls;
71848
71848
  };
71849
71849
 
71850
+ // lib/site/serialize-script-value.ts
71851
+ var serializeScriptValue = (value) => JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
71852
+
71850
71853
  // lib/site/getIndex.ts
71851
- var getIndex = async (mainComponentPath, fileServerApiBaseUrl, jscdnBaseUrl) => {
71854
+ var getIndex = async ({
71855
+ mainComponentPath,
71856
+ fileServerApiBaseUrl,
71857
+ jscdnBaseUrl,
71858
+ extraRunFrameTabs = []
71859
+ } = {}) => {
71852
71860
  const standalone = process.env.TSCIRCUIT_STANDALONE === "1";
71853
71861
  const evalVersion = package_default.devDependencies["@tscircuit/eval"];
71854
71862
  const standaloneScript = standalone ? `
@@ -71872,14 +71880,16 @@ var getIndex = async (mainComponentPath, fileServerApiBaseUrl, jscdnBaseUrl) =>
71872
71880
  </head>
71873
71881
  <body>
71874
71882
  <script>
71875
- ${mainComponentPath ? `window.TSCIRCUIT_DEFAULT_MAIN_COMPONENT_PATH = "${mainComponentPath}";` : ""}
71883
+ ${mainComponentPath ? `window.TSCIRCUIT_DEFAULT_MAIN_COMPONENT_PATH = ${serializeScriptValue(mainComponentPath)};` : ""}
71876
71884
  window.TSCIRCUIT_USE_RUNFRAME_FOR_CLI = true;${tokenScript}${fileServerApiScript}${jscdnScript}${partsEngineScript}${standaloneScript}
71885
+ window.TSCIRCUIT_EXTRA_RUNFRAME_TABS = ${serializeScriptValue(extraRunFrameTabs)};
71877
71886
  </script>
71878
71887
  ${standalone ? "" : '<script src="https://cdn.tailwindcss.com"></script>'}
71879
71888
  <div id="root">loading...</div>
71880
71889
  <script>
71881
71890
  globalThis.process = { env: { NODE_ENV: "production" } }
71882
71891
  </script>
71892
+ ${fileServerApiBaseUrl ? '<script src="/extra-runframe-tabs.js" defer></script>' : ""}
71883
71893
  <script type="module" src="/standalone.min.js"></script>
71884
71894
  </body>
71885
71895
  </html>`;
@@ -79942,11 +79952,28 @@ var kicadPcmSettingsSchema = z21.object({
79942
79952
  schemaVersion: z21.union([z21.literal("auto"), z21.literal(1), z21.literal(2)]).optional(),
79943
79953
  license: z21.string().trim().min(1).optional()
79944
79954
  });
79955
+ var extraRunFrameTabSchema = z21.object({
79956
+ tabName: z21.string().trim().min(1),
79957
+ iframeUrl: z21.string().url().refine((url) => /^https?:\/\//i.test(url), {
79958
+ message: "Extra RunFrame tabs require an HTTP or HTTPS URL"
79959
+ }).refine((url) => {
79960
+ try {
79961
+ const parsed = new URL(url);
79962
+ return !parsed.username && !parsed.password;
79963
+ } catch {
79964
+ return false;
79965
+ }
79966
+ }, {
79967
+ message: "Extra RunFrame tab URLs must not include credentials"
79968
+ }),
79969
+ mainComponentPath: z21.string().trim().min(1).optional()
79970
+ });
79945
79971
  var projectConfigSchema = z21.object({
79946
79972
  mainEntrypoint: z21.string().optional(),
79947
79973
  libraryEntrypoint: z21.string().optional(),
79948
79974
  previewComponentPath: z21.string().optional(),
79949
79975
  siteDefaultComponentPath: z21.string().optional(),
79976
+ extraRunFrameTabs: z21.array(extraRunFrameTabSchema).optional(),
79950
79977
  ignoredFiles: z21.array(z21.string()).optional(),
79951
79978
  includeBoardFiles: z21.array(z21.string()).optional(),
79952
79979
  snapshotsDir: z21.string().optional(),
@@ -80029,7 +80056,7 @@ var loadProjectEnv = (projectDir) => {
80029
80056
  }
80030
80057
  }
80031
80058
  };
80032
- var loadProjectConfigSync = (projectDir = process.cwd()) => {
80059
+ var loadProjectConfigSync = (projectDir = process.cwd(), options = {}) => {
80033
80060
  const configPath = path11.join(projectDir, CONFIG_FILENAME);
80034
80061
  if (!fs9.existsSync(configPath)) {
80035
80062
  return null;
@@ -80039,11 +80066,13 @@ var loadProjectConfigSync = (projectDir = process.cwd()) => {
80039
80066
  const parsedConfig = JSON.parse(configContent);
80040
80067
  return projectConfigSchema.parse(parsedConfig);
80041
80068
  } catch (error) {
80069
+ if (options.throwOnError)
80070
+ throw error;
80042
80071
  console.error(`Error loading tscircuit config: ${error}`);
80043
80072
  return null;
80044
80073
  }
80045
80074
  };
80046
- var loadProjectConfigModule = async (projectDir) => {
80075
+ var loadProjectConfigModule = async (projectDir, options = {}) => {
80047
80076
  loadProjectEnv(projectDir);
80048
80077
  for (const configFileName of CONFIG_MODULE_FILENAMES) {
80049
80078
  const configPath = path11.join(projectDir, configFileName);
@@ -80057,15 +80086,17 @@ var loadProjectConfigModule = async (projectDir) => {
80057
80086
  const exportedConfig = importedModule.default ?? importedModule.config ?? importedModule;
80058
80087
  return parseProjectConfigObject(exportedConfig);
80059
80088
  } catch (error) {
80089
+ if (options.throwOnError)
80090
+ throw error;
80060
80091
  console.error(`Error loading ${configFileName}: ${error}`);
80061
80092
  return null;
80062
80093
  }
80063
80094
  }
80064
80095
  return null;
80065
80096
  };
80066
- var loadRuntimeProjectConfig = async (projectDir = process.cwd()) => {
80067
- const jsonConfig = loadProjectConfigSync(projectDir);
80068
- const moduleConfig = await loadProjectConfigModule(projectDir);
80097
+ var loadRuntimeProjectConfig = async (projectDir = process.cwd(), options = {}) => {
80098
+ const jsonConfig = loadProjectConfigSync(projectDir, options);
80099
+ const moduleConfig = await loadProjectConfigModule(projectDir, options);
80069
80100
  if (!jsonConfig && !moduleConfig) {
80070
80101
  return null;
80071
80102
  }
@@ -80846,6 +80877,84 @@ var copyJscdnFiles = async (sourceDir, outputDir) => {
80846
80877
  await cp(source, output, { recursive: true, dereference: true });
80847
80878
  };
80848
80879
 
80880
+ // lib/server/read-extra-runframe-tabs.ts
80881
+ async function readExtraRunFrameTabs(projectDir) {
80882
+ const config = projectDir ? await loadRuntimeProjectConfig(projectDir, { throwOnError: true }) : null;
80883
+ return {
80884
+ tabs: (config?.extraRunFrameTabs ?? []).map((tab) => ({
80885
+ tab_name: tab.tabName,
80886
+ iframe_url: tab.iframeUrl,
80887
+ ...tab.mainComponentPath === undefined ? {} : { main_component_path: tab.mainComponentPath }
80888
+ }))
80889
+ };
80890
+ }
80891
+
80892
+ // lib/site/extra-runframe-tabs-client-script.ts
80893
+ var extraRunFrameTabsClientScript = `{
80894
+ let stopped = false;
80895
+ let timeout;
80896
+ let controller;
80897
+ let lifecycleRevision = 0;
80898
+ let previousTabs = JSON.stringify(window.TSCIRCUIT_EXTRA_RUNFRAME_TABS || []);
80899
+ const errorId = "tscircuit-external-tabs-error";
80900
+
80901
+ const refreshExternalTabs = async () => {
80902
+ const revision = lifecycleRevision;
80903
+ const requestController = new AbortController();
80904
+ controller = requestController;
80905
+ const requestTimeout = setTimeout(() => requestController.abort(), 10000);
80906
+ try {
80907
+ const response = await fetch("/api/extra_runframe_tabs/list", {
80908
+ cache: "no-store",
80909
+ signal: requestController.signal
80910
+ });
80911
+ if (!response.ok) throw new Error("External tabs request failed");
80912
+ const payload = await response.json();
80913
+ if (!Array.isArray(payload.tabs)) throw new Error("Invalid external tabs response");
80914
+ const tabs = payload.tabs.map((tab) => ({
80915
+ tabName: tab.tab_name,
80916
+ iframeUrl: tab.iframe_url,
80917
+ ...(tab.main_component_path === undefined ? {} : { mainComponentPath: tab.main_component_path })
80918
+ }));
80919
+ clearTimeout(requestTimeout);
80920
+ if (stopped || revision !== lifecycleRevision) return;
80921
+ const serializedTabs = JSON.stringify(tabs);
80922
+ if (serializedTabs !== previousTabs) {
80923
+ window.TSCIRCUIT_EXTRA_RUNFRAME_TABS = tabs;
80924
+ window.dispatchEvent(new Event("tscircuit:extra-runframe-tabs-change"));
80925
+ previousTabs = serializedTabs;
80926
+ }
80927
+ document.getElementById(errorId)?.remove();
80928
+ } catch (error) {
80929
+ clearTimeout(requestTimeout);
80930
+ if (stopped || revision !== lifecycleRevision) return;
80931
+ if (!document.getElementById(errorId)) {
80932
+ const alert = document.createElement("div");
80933
+ alert.id = errorId;
80934
+ alert.setAttribute("role", "alert");
80935
+ alert.textContent = "External tabs could not update. The displayed pages may be out of date. Check the dev server and project configuration.";
80936
+ alert.style.cssText = "position:fixed;bottom:12px;left:12px;right:12px;z-index:2147483647;padding:12px;background:#7f1d1d;color:white;font:14px sans-serif";
80937
+ document.body.append(alert);
80938
+ }
80939
+ }
80940
+ if (!stopped && revision === lifecycleRevision) timeout = setTimeout(refreshExternalTabs, 1000);
80941
+ };
80942
+
80943
+ window.addEventListener("pagehide", () => {
80944
+ stopped = true;
80945
+ lifecycleRevision++;
80946
+ clearTimeout(timeout);
80947
+ controller?.abort();
80948
+ });
80949
+ window.addEventListener("pageshow", (event) => {
80950
+ if (event.persisted && stopped) {
80951
+ stopped = false;
80952
+ void refreshExternalTabs();
80953
+ }
80954
+ });
80955
+ void refreshExternalTabs();
80956
+ }`;
80957
+
80849
80958
  // lib/server/createHttpServer.ts
80850
80959
  var RUNFRAME_CACHE_PATH = "/api/cache";
80851
80960
  var resolveLocalTscircuitStandalonePath = (projectDir) => {
@@ -80881,6 +80990,42 @@ var createHttpServer = async ({
80881
80990
  const server = http.createServer(async (req, res) => {
80882
80991
  const requestHost = req.headers.host ?? `localhost:${port}`;
80883
80992
  const url = new URL(req.url, `http://${requestHost}`);
80993
+ if (url.pathname === "/extra-runframe-tabs.js") {
80994
+ res.writeHead(200, {
80995
+ "Content-Type": "application/javascript; charset=utf-8",
80996
+ "Cache-Control": "no-store"
80997
+ });
80998
+ res.end(extraRunFrameTabsClientScript);
80999
+ return;
81000
+ }
81001
+ if (url.pathname === "/api/extra_runframe_tabs/list") {
81002
+ if (req.method !== "GET" && req.method !== "POST") {
81003
+ res.writeHead(405, { Allow: "GET, POST" });
81004
+ res.end();
81005
+ return;
81006
+ }
81007
+ try {
81008
+ const response = await readExtraRunFrameTabs(projectDir);
81009
+ res.writeHead(200, {
81010
+ "Content-Type": "application/json",
81011
+ "Cache-Control": "no-store"
81012
+ });
81013
+ res.end(JSON.stringify(response));
81014
+ } catch (error) {
81015
+ console.error("Unable to refresh external RunFrame tabs:", error);
81016
+ res.writeHead(500, {
81017
+ "Content-Type": "application/json",
81018
+ "Cache-Control": "no-store"
81019
+ });
81020
+ res.end(JSON.stringify({
81021
+ error: {
81022
+ error_code: "external_tabs_config_invalid",
81023
+ message: "Unable to load external tabs. Check the project configuration."
81024
+ }
81025
+ }));
81026
+ }
81027
+ return;
81028
+ }
80884
81029
  if (preparedJscdnDir && url.pathname.startsWith("/jscdn/")) {
80885
81030
  if (req.method !== "GET" && req.method !== "HEAD") {
80886
81031
  res.writeHead(405, { Allow: "GET, HEAD" });
@@ -81034,10 +81179,22 @@ var createHttpServer = async ({
81034
81179
  return;
81035
81180
  }
81036
81181
  if (url.pathname === "/") {
81037
- const fileServerApiBaseUrl = `http://${req.headers.host}/api`;
81038
- const html = await getIndex(defaultMainComponentPath, fileServerApiBaseUrl, preparedJscdnDir ? "/jscdn" : undefined);
81039
- res.writeHead(200, { "Content-Type": "text/html" });
81040
- res.end(html);
81182
+ try {
81183
+ const projectConfig = projectDir ? await loadRuntimeProjectConfig(projectDir, { throwOnError: true }) : null;
81184
+ const fileServerApiBaseUrl = `http://${req.headers.host}/api`;
81185
+ const html = await getIndex({
81186
+ mainComponentPath: defaultMainComponentPath,
81187
+ fileServerApiBaseUrl,
81188
+ jscdnBaseUrl: preparedJscdnDir ? "/jscdn" : undefined,
81189
+ extraRunFrameTabs: projectConfig?.extraRunFrameTabs
81190
+ });
81191
+ res.writeHead(200, { "Content-Type": "text/html" });
81192
+ res.end(html);
81193
+ } catch (error) {
81194
+ console.error("Failed to render the RunFrame page:", error);
81195
+ res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
81196
+ res.end("Unable to load project configuration. Check the CLI output and reload.");
81197
+ }
81041
81198
  return;
81042
81199
  }
81043
81200
  if (pcmProxy && url.pathname.startsWith("/pcm")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/cli",
3
- "version": "0.1.2383",
3
+ "version": "0.1.2384",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/tscircuit/cli"
@@ -155,6 +155,33 @@
155
155
  }
156
156
  },
157
157
  "description": "Build output toggles."
158
+ },
159
+ "extraRunFrameTabs": {
160
+ "type": "array",
161
+ "description": "Additional iframe tabs for tsci dev and build --site. Requires a RunFrame bundle with extra tab support.",
162
+ "items": {
163
+ "type": "object",
164
+ "additionalProperties": false,
165
+ "required": ["tabName", "iframeUrl"],
166
+ "properties": {
167
+ "tabName": {
168
+ "type": "string",
169
+ "minLength": 1,
170
+ "description": "Label displayed in RunFrame."
171
+ },
172
+ "iframeUrl": {
173
+ "type": "string",
174
+ "format": "uri",
175
+ "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://",
176
+ "description": "HTTP or HTTPS iframe URL, including encoded SysConfig content in its fragment."
177
+ },
178
+ "mainComponentPath": {
179
+ "type": "string",
180
+ "minLength": 1,
181
+ "description": "Optional project-relative circuit path. Omit to show this tab for every circuit."
182
+ }
183
+ }
184
+ }
158
185
  }
159
186
  }
160
187
  }