@hitch42/applet 0.1.1-beta.15 → 0.1.1-beta.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/_internal/server.d.ts +10 -1
  2. package/dist/_internal/server.js +16 -2
  3. package/dist/applet.js +12 -12
  4. package/dist/auth-D3CrCtZq.js +21 -0
  5. package/dist/{build-BkhBHdMs.js → build-XHoa_sJL.js} +1 -1
  6. package/dist/build-ueR7XjZT.js +2 -0
  7. package/dist/{ci-BKw14X2h.js → ci-B5-mH-YW.js} +1 -1
  8. package/dist/client.d.ts +3 -3
  9. package/dist/contract-BbOj5EB7.js +23 -0
  10. package/dist/{contract-CZdT22tB.d.ts → contract-D9wEvxGI.d.ts} +1 -1
  11. package/dist/{deploy-D5SBBd-j.js → deploy-C5JDIn2Y.js} +4 -3
  12. package/dist/device-BgIfytaR.js +135 -0
  13. package/dist/email-protocol-B9u_dh2p.d.ts +202 -0
  14. package/dist/{generate-MWjUbZnO.js → generate-BV_emZgy.js} +39 -13
  15. package/dist/generate-C9bWqdaB.js +2 -0
  16. package/dist/hitch-file-url-BPKj9zVG.js +25 -0
  17. package/dist/index.d.ts +15 -25
  18. package/dist/index.js +80 -192
  19. package/dist/{init-Dw7GEI5v.js → init-BGQ_bP44.js} +5 -5
  20. package/dist/{manifest-CuY3ICpN.js → load-config-Dkr2VxZN.js} +37 -2
  21. package/dist/{login-CUx3TmFQ.js → login-CceGvlMi.js} +3 -1
  22. package/dist/{logs-BySh9fnH.js → logs-DOkT5ots.js} +3 -3
  23. package/dist/{manifest-ARAy2HP7.d.ts → manifest-BSUtHbu-.d.ts} +1 -4
  24. package/dist/markdown-CwlqzBtP.js +88 -0
  25. package/dist/markdown-DSHOQIOb.d.ts +14 -0
  26. package/dist/markdown-editor-BmEaBzd7.d.ts +27 -0
  27. package/dist/markdown-editor-C5mLHGqu.js +428 -0
  28. package/dist/{project-NcWw8SwL.js → project-CCVqCCgU.js} +3 -2
  29. package/dist/react/markdown-editor.d.ts +2 -0
  30. package/dist/react/markdown-editor.js +2 -0
  31. package/dist/react/markdown.d.ts +2 -0
  32. package/dist/react/markdown.js +2 -0
  33. package/dist/react.d.ts +2 -37
  34. package/dist/react.js +2 -534
  35. package/dist/{schedules-B_p9U-vM.js → schedules-D6-3v-Az.js} +1 -1
  36. package/dist/server.d.ts +3 -3
  37. package/dist/store-B8N_8JIN.js +196 -0
  38. package/dist/{upload-DKZzz5kW.d.ts → upload-D_kkmne_.d.ts} +5 -9
  39. package/dist/{variables-D9nEFyOK.js → variables-BSFZnLxY.js} +2 -2
  40. package/dist/vite.d.ts +1 -1
  41. package/dist/vite.js +165 -35
  42. package/dist/workspace-8yqXsvce.js +31 -0
  43. package/package.json +13 -1
  44. package/scaffold/.agents/skills/hitch-applets/references/markdown.md +20 -2
  45. package/dist/build-D7FDW8iC.js +0 -2
  46. package/dist/email-WA4jnzZ0.js +0 -211
  47. package/dist/email-protocol-DqJ6YCiC.d.ts +0 -230
  48. package/dist/generate-WYS-jtPH.js +0 -2
  49. package/dist/load-config-BeQZ2yOa.js +0 -27
  50. package/dist/me-BW7Paary.js +0 -32
  51. package/dist/workspace-D0dnnlxL.js +0 -385
@@ -0,0 +1,196 @@
1
+ import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ //#region ../shared/auth/src/store.mts
5
+ const DEFAULT_INSTANCE_URL = "https://app.hitch42.com";
6
+ var AuthenticationError = class extends Error {};
7
+ function isErrno(error, code) {
8
+ return error instanceof Error && "code" in error && error.code === code;
9
+ }
10
+ async function readJson(path) {
11
+ try {
12
+ return JSON.parse(await readFile(path, "utf8"));
13
+ } catch (error) {
14
+ if (isErrno(error, "ENOENT")) return void 0;
15
+ throw new Error(`Failed to read ${path}.`, { cause: error });
16
+ }
17
+ }
18
+ function isObject(value) {
19
+ return value !== null && typeof value === "object" && !Array.isArray(value);
20
+ }
21
+ function emptyCredentialStore() {
22
+ return { instances: {} };
23
+ }
24
+ async function healCredentialStore(credentialsPath, source, store, unrecognizedCount) {
25
+ const backupPath = `${credentialsPath}.bak`;
26
+ try {
27
+ try {
28
+ await writeFile(backupPath, source, {
29
+ flag: "wx",
30
+ mode: 384
31
+ });
32
+ } catch (error) {
33
+ if (!isErrno(error, "EEXIST")) throw error;
34
+ }
35
+ await chmod(backupPath, 384);
36
+ await writeFile(credentialsPath, `${JSON.stringify(store, null, 2)}\n`, { mode: 384 });
37
+ await chmod(credentialsPath, 384);
38
+ } catch {
39
+ console.error(unrecognizedCount === void 0 ? `hitch: failed to heal credential store at ${credentialsPath}; using an empty in-memory store.` : `hitch: failed to heal credential store at ${credentialsPath}; using recognized credentials in memory only.`);
40
+ return store;
41
+ }
42
+ console.error(unrecognizedCount === void 0 ? `hitch: reset unreadable credentials file (backup: ${backupPath}).` : unrecognizedCount === 0 ? `hitch: migrated credentials to per-organization storage (backup: ${backupPath}).` : `hitch: removed ${unrecognizedCount} unrecognized credential ${unrecognizedCount === 1 ? "entry" : "entries"} (backup: ${backupPath}).`);
43
+ return store;
44
+ }
45
+ async function readRawCredentialStore() {
46
+ const { credentialsPath } = storePaths();
47
+ let source;
48
+ try {
49
+ source = await readFile(credentialsPath, "utf8");
50
+ } catch (error) {
51
+ if (isErrno(error, "ENOENT")) return emptyCredentialStore();
52
+ throw new Error(`Failed to read ${credentialsPath}.`, { cause: error });
53
+ }
54
+ let value;
55
+ try {
56
+ value = JSON.parse(source);
57
+ } catch {
58
+ return healCredentialStore(credentialsPath, source, emptyCredentialStore());
59
+ }
60
+ if (!isObject(value) || !isObject(value.instances)) return healCredentialStore(credentialsPath, source, emptyCredentialStore());
61
+ const instances = Object.fromEntries(Object.entries(value.instances).flatMap(([instanceUrl, entry]) => {
62
+ if (isObject(entry) && typeof entry.organizationId === "string") {
63
+ const credentials = parseCredentials(entry);
64
+ return credentials === void 0 ? [] : [[instanceUrl, { [credentials.orgCode]: entry }]];
65
+ }
66
+ if (!isObject(entry)) return [];
67
+ return [[instanceUrl, Object.fromEntries(Object.entries(entry).filter(([orgCode, credentials]) => parseCredentials(credentials)?.orgCode === orgCode))]];
68
+ }));
69
+ const reshaped = Object.values(value.instances).some((entry) => !isObject(entry) || typeof entry.organizationId === "string");
70
+ const countLeaves = (store) => Object.values(store).reduce((total, entry) => total + (isObject(entry) && typeof entry.organizationId !== "string" ? Object.keys(entry).length : 1), 0);
71
+ const unrecognizedCount = countLeaves(value.instances) - countLeaves(instances);
72
+ if (!reshaped && unrecognizedCount === 0) return value;
73
+ return healCredentialStore(credentialsPath, source, { instances }, unrecognizedCount);
74
+ }
75
+ function parseCredentials(value) {
76
+ if (!isObject(value) || typeof value.organizationId !== "string" || typeof value.orgCode !== "string") return;
77
+ const organization = {
78
+ organizationId: value.organizationId,
79
+ orgCode: value.orgCode
80
+ };
81
+ if ((value.type === void 0 || value.type === "session") && typeof value.accessToken === "string" && (value.expiresAt === void 0 || typeof value.expiresAt === "number")) return {
82
+ type: "session",
83
+ accessToken: value.accessToken,
84
+ ...value.expiresAt === void 0 ? {} : { expiresAt: value.expiresAt },
85
+ ...organization
86
+ };
87
+ if (value.type === "apiKey" && typeof value.apiKey === "string") return {
88
+ type: "apiKey",
89
+ apiKey: value.apiKey,
90
+ ...organization
91
+ };
92
+ }
93
+ function normalizeInstanceUrl(instanceUrl) {
94
+ let url;
95
+ try {
96
+ url = new URL(instanceUrl);
97
+ } catch (error) {
98
+ throw new Error(`Invalid Hitch instance URL: ${instanceUrl}`, { cause: error });
99
+ }
100
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error(`Invalid Hitch instance URL protocol: ${url.protocol}`);
101
+ url.pathname = url.pathname.replace(/\/+$/, "");
102
+ url.search = "";
103
+ url.hash = "";
104
+ return url.toString().replace(/\/$/, "");
105
+ }
106
+ function organizationInstanceUrl(instanceUrl, orgCode) {
107
+ const url = new URL(normalizeInstanceUrl(instanceUrl));
108
+ const labels = url.hostname.split(".");
109
+ if (labels[0] !== "app" || labels.length < 2) return url.toString().replace(/\/$/, "");
110
+ url.hostname = [orgCode, ...labels.slice(1)].join(".");
111
+ return url.toString().replace(/\/$/, "");
112
+ }
113
+ function storePaths() {
114
+ const env = process.env;
115
+ const home = homedir();
116
+ const configDir = join(env.XDG_CONFIG_HOME || join(home, ".config"), "hitch");
117
+ return {
118
+ configDir,
119
+ cacheDir: join(env.XDG_CACHE_HOME || join(home, ".cache"), "hitch"),
120
+ configPath: join(configDir, "config.json"),
121
+ credentialsPath: join(configDir, "credentials.json")
122
+ };
123
+ }
124
+ async function resolveInstanceUrl(override) {
125
+ if (override !== void 0) return normalizeInstanceUrl(override);
126
+ const envInstanceUrl = process.env.HITCH_API_URL;
127
+ if (envInstanceUrl !== void 0 && envInstanceUrl.length > 0) return normalizeInstanceUrl(envInstanceUrl);
128
+ const { configPath } = storePaths();
129
+ const config = await readJson(configPath);
130
+ if (config === void 0) return DEFAULT_INSTANCE_URL;
131
+ if (!isObject(config) || config.instanceUrl !== void 0 && typeof config.instanceUrl !== "string") throw new Error(`${configPath}: expected { instanceUrl?: string }.`);
132
+ return normalizeInstanceUrl(config.instanceUrl ?? "https://app.hitch42.com");
133
+ }
134
+ async function readCredentialStore() {
135
+ const store = await readRawCredentialStore();
136
+ return { instances: Object.fromEntries(Object.entries(store.instances).map(([instanceUrl, credentialsByOrg]) => [instanceUrl, Object.fromEntries(Object.entries(credentialsByOrg).flatMap(([orgCode, value]) => {
137
+ const credentials = parseCredentials(value);
138
+ return credentials === void 0 ? [] : [[orgCode, credentials]];
139
+ }))])) };
140
+ }
141
+ async function getCredentialsForOrg(instanceUrl, orgCode) {
142
+ return (await readCredentialStore()).instances[normalizeInstanceUrl(instanceUrl)]?.[orgCode];
143
+ }
144
+ function environmentAuth() {
145
+ const apiKey = process.env.HITCH_API_KEY;
146
+ return apiKey !== void 0 && apiKey.length > 0 ? {
147
+ accessToken: apiKey,
148
+ source: "environment"
149
+ } : void 0;
150
+ }
151
+ function resolvedStoredAuth(credentials) {
152
+ return credentials === void 0 ? void 0 : {
153
+ accessToken: credentials.type === "apiKey" ? credentials.apiKey : credentials.accessToken,
154
+ credentialType: credentials.type,
155
+ organizationId: credentials.organizationId,
156
+ orgCode: credentials.orgCode,
157
+ source: "store"
158
+ };
159
+ }
160
+ function resolveStoredAuthForOrg(credentialsByOrg, orgCode) {
161
+ const exact = resolvedStoredAuth(credentialsByOrg[orgCode]);
162
+ if (exact !== void 0) return exact;
163
+ const sessions = Object.values(credentialsByOrg).filter((credentials) => credentials.type === "session").filter((credentials, index, all) => all.findIndex(({ accessToken }) => accessToken === credentials.accessToken) === index);
164
+ if (sessions.length === 1) return resolvedStoredAuth(sessions[0]);
165
+ if (sessions.length > 1) throw new AuthenticationError(`Multiple sessions are stored. Run hitch login for organization "${orgCode}".`);
166
+ const apiKeyOrgs = Object.values(credentialsByOrg).filter((credentials) => credentials.type === "apiKey").map(({ orgCode: storedOrg }) => storedOrg).sort();
167
+ if (apiKeyOrgs.length > 0) throw new AuthenticationError(`Stored API ${apiKeyOrgs.length === 1 ? "key is" : "keys are"} scoped to ${apiKeyOrgs.map((storedOrg) => `"${storedOrg}"`).join(", ")}, not "${orgCode}".`);
168
+ }
169
+ async function resolveAuthForOrg(instanceUrl, orgCode) {
170
+ const auth = environmentAuth();
171
+ if (auth !== void 0) return auth;
172
+ const normalizedUrl = normalizeInstanceUrl(instanceUrl);
173
+ const credentialsByOrg = Object.entries((await readCredentialStore()).instances).find(([instance]) => instance === normalizedUrl)?.[1];
174
+ return resolveStoredAuthForOrg(credentialsByOrg ?? {}, orgCode);
175
+ }
176
+ function rejectEnvironmentKey(response, auth) {
177
+ if (auth.source === "environment" && response.status === 401) throw new AuthenticationError("HITCH_API_KEY was rejected.");
178
+ }
179
+ function bearerToken(credentials) {
180
+ return "apiKey" in credentials ? credentials.apiKey : credentials.accessToken;
181
+ }
182
+ async function setCredentials(instanceUrl, credentials) {
183
+ const paths = storePaths();
184
+ const store = await readRawCredentialStore();
185
+ const normalizedUrl = normalizeInstanceUrl(instanceUrl);
186
+ const credentialsByOrg = store.instances[normalizedUrl];
187
+ store.instances[normalizedUrl] = {
188
+ ...isObject(credentialsByOrg) ? credentialsByOrg : {},
189
+ [credentials.orgCode]: credentials
190
+ };
191
+ await mkdir(paths.configDir, { recursive: true });
192
+ await writeFile(paths.credentialsPath, `${JSON.stringify(store, null, 2)}\n`, { mode: 384 });
193
+ await chmod(paths.credentialsPath, 384);
194
+ }
195
+ //#endregion
196
+ export { organizationInstanceUrl as a, resolveInstanceUrl as c, getCredentialsForOrg as i, setCredentials as l, DEFAULT_INSTANCE_URL as n, rejectEnvironmentKey as o, bearerToken as r, resolveAuthForOrg as s, AuthenticationError as t };
@@ -1,5 +1,4 @@
1
- import { ContractProcedure, Meta } from "@orpc/contract";
2
- import { z } from "zod";
1
+ import { ContractProcedure, Meta, Schema } from "@orpc/contract";
3
2
  //#region src/hitch/files.d.ts
4
3
  type HitchFile = {
5
4
  id: string;
@@ -36,13 +35,10 @@ type FileOutput = {
36
35
  data: HitchFile;
37
36
  downloadUrl: string;
38
37
  };
39
- declare const fileSchema: z.ZodType<HitchFile, HitchFile>;
40
- declare const createFileOutputSchema: z.ZodType<CreateFileOutput, CreateFileOutput>;
41
- declare const getFileOutputSchema: z.ZodType<FileOutput, FileOutput>;
42
38
  type FilesContract = {
43
- create: ContractProcedure<z.ZodType<CreateFileInput, CreateFileInput>, z.ZodType<CreateFileOutput, CreateFileOutput>, Record<never, never>, Meta>;
44
- confirm: ContractProcedure<z.ZodType<FileIdInput, FileIdInput>, z.ZodType<FileOutput, FileOutput>, Record<never, never>, Meta>;
45
- get: ContractProcedure<z.ZodType<FileIdInput, FileIdInput>, z.ZodType<FileOutput, FileOutput>, Record<never, never>, Meta>;
39
+ create: ContractProcedure<Schema<CreateFileInput, CreateFileInput>, Schema<CreateFileOutput, CreateFileOutput>, Record<never, never>, Meta>;
40
+ confirm: ContractProcedure<Schema<FileIdInput, FileIdInput>, Schema<FileOutput, FileOutput>, Record<never, never>, Meta>;
41
+ get: ContractProcedure<Schema<FileIdInput, FileIdInput>, Schema<FileOutput, FileOutput>, Record<never, never>, Meta>;
46
42
  };
47
43
  declare const filesContract: FilesContract;
48
44
  //#endregion
@@ -109,4 +105,4 @@ type WithFilesUpload<T> = T extends {
109
105
  files: FilesUpload;
110
106
  };
111
107
  //#endregion
112
- export { UploadedHitchFile as a, createFileOutputSchema as c, getFileOutputSchema as d, UploadProgress as i, fileSchema as l, FilesUpload as n, WithFilesUpload as o, UploadFileOptions as r, uploadFile as s, FilesClient as t, filesContract as u };
108
+ export { UploadedHitchFile as a, filesContract as c, UploadProgress as i, FilesUpload as n, WithFilesUpload as o, UploadFileOptions as r, uploadFile as s, FilesClient as t };
@@ -1,5 +1,5 @@
1
- import { n as fetchWithOrgAuth } from "./workspace-D0dnnlxL.js";
2
- import { t as loadAppletConfig } from "./load-config-BeQZ2yOa.js";
1
+ import { t as fetchWithOrgAuth } from "./auth-D3CrCtZq.js";
2
+ import { t as loadAppletConfig } from "./load-config-Dkr2VxZN.js";
3
3
  import { t as question } from "./prompt-ChS-grYO.js";
4
4
  import { z } from "zod";
5
5
  //#region src/variables.ts
package/dist/vite.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { o as RegisteredAppletSourceDefinition } from "./manifest-ARAy2HP7.js";
1
+ import { o as RegisteredAppletSourceDefinition } from "./manifest-BSUtHbu-.js";
2
2
  import { PluginOption } from "vite";
3
3
  //#region src/vite.d.ts
4
4
  declare function applet<const M extends RegisteredAppletSourceDefinition>(sourceDefinition: M): PluginOption[];
package/dist/vite.js CHANGED
@@ -1,18 +1,22 @@
1
- import { n as meSchema } from "./me-BW7Paary.js";
2
- import { i as APPLET_CODE_PATTERN, n as appletManifestSchema, r as appletSourceDefinitionSchema } from "./manifest-CuY3ICpN.js";
1
+ import { t as pruneContract } from "./contract-BbOj5EB7.js";
3
2
  import { a as openQueryCacheDatabase } from "./query-cache-CXmAyLv3.js";
4
3
  import { d as HITCH_USER_HEADER, u as HITCH_SESSION_HEADER } from "./headers-DkNr9MLJ.js";
5
4
  import { a as workflowOutcomeSchema, n as runWorkflowJournal } from "./workflow-engine-BOTzURKM.js";
6
- import { c as bearerToken, d as resolveAuthForOrg, f as resolveInstanceUrl, l as organizationInstanceUrl, t as resolveWorkspaceOrg, u as rejectEnvironmentKey } from "./workspace-D0dnnlxL.js";
7
- import { c as writeClientEntry, i as writeRegisterDeclaration, l as writeManifestGen, r as syncContract, s as writeAppletRoot, u as writeServerEntry } from "./generate-MWjUbZnO.js";
5
+ import { a as organizationInstanceUrl, c as resolveInstanceUrl, o as rejectEnvironmentKey, r as bearerToken, s as resolveAuthForOrg } from "./store-B8N_8JIN.js";
6
+ import { a as writeRegisterDeclaration, c as writeAppletRoot, d as writeServerEntry, i as syncContract, l as writeClientEntry, n as routesModuleSource, u as writeManifestGen } from "./generate-BV_emZgy.js";
7
+ import { a as appletSourceDefinitionSchema, i as appletManifestSchema, o as APPLET_CODE_PATTERN, r as resolveWorkspaceOrg } from "./load-config-Dkr2VxZN.js";
8
+ import { z } from "zod";
8
9
  import { randomUUID } from "node:crypto";
9
10
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
10
11
  import { appendFile, readdir, rm, writeFile } from "node:fs/promises";
11
12
  import { createServer } from "node:http";
12
13
  import { basename, dirname, relative, resolve } from "node:path";
14
+ import { pathToFileURL } from "node:url";
13
15
  import { cloudflare } from "@cloudflare/vite-plugin";
16
+ import optimizeLocales from "@react-aria/optimize-locales-plugin";
14
17
  import { tanstackRouter } from "@tanstack/router-plugin/vite";
15
18
  import react from "@vitejs/plugin-react";
19
+ import { normalizePath } from "vite";
16
20
  import { Readable } from "node:stream";
17
21
  import { pipeline } from "node:stream/promises";
18
22
  //#region src/dev-proxy.ts
@@ -292,6 +296,31 @@ function createDevWorkflows(options) {
292
296
  };
293
297
  }
294
298
  //#endregion
299
+ //#region src/hitch/me-schemas.ts
300
+ const personSchema = z.object({
301
+ id: z.uuid(),
302
+ firstName: z.string(),
303
+ lastName: z.string(),
304
+ email: z.email()
305
+ });
306
+ const membershipSchema = z.object({
307
+ orgId: z.uuid(),
308
+ code: z.string(),
309
+ name: z.string(),
310
+ iconFileId: z.uuid().nullable(),
311
+ role: z.enum(["member", "owner"]),
312
+ capabilities: z.array(z.string())
313
+ });
314
+ const meSchema = z.object({
315
+ person: personSchema,
316
+ memberships: z.array(membershipSchema),
317
+ organization: z.object({
318
+ id: z.uuid(),
319
+ code: z.string()
320
+ }).nullable(),
321
+ multiOrg: z.boolean()
322
+ });
323
+ //#endregion
295
324
  //#region src/vite.ts
296
325
  const APPLET_COMPATIBILITY_DATE = "2026-07-14";
297
326
  const APPLET_COMPATIBILITY_FLAGS = ["nodejs_compat"];
@@ -302,12 +331,20 @@ function clientBootstrapScript(parentOrigin, appletCode, cacheQueries) {
302
331
  var appletCode = ${JSON.stringify(appletCode)};
303
332
  var appletCodePattern = ${APPLET_CODE_PATTERN.toString()};
304
333
  var cacheQueries = ${cacheQueries} && window.parent !== window && parentOrigin !== null;
334
+ var cacheContextElement = document.getElementById("hitch-cache-context");
305
335
 
306
336
  Object.defineProperty(window, "__hitchCacheContext", {
307
337
  configurable: false,
308
338
  writable: false,
309
339
  value: cacheQueries
310
- ? fetch("/__applet/context/" + encodeURIComponent(appletCode), {
340
+ ? cacheContextElement
341
+ ? Promise.resolve().then(function () {
342
+ return JSON.parse(cacheContextElement.textContent);
343
+ }).catch(function (error) {
344
+ console.error("Could not load the applet cache context.", error);
345
+ return null;
346
+ })
347
+ : fetch("/__applet/context/" + encodeURIComponent(appletCode), {
311
348
  credentials: "same-origin",
312
349
  headers: { accept: "application/json" }
313
350
  }).then(function (response) {
@@ -349,6 +386,52 @@ function clientBootstrapScript(parentOrigin, appletCode, cacheQueries) {
349
386
  );
350
387
  }
351
388
 
389
+ function prefetchAppletAssets(code) {
390
+ var key = "hitch:prefetched:" + code;
391
+ function clearPrefetchRecord() {
392
+ try {
393
+ window.sessionStorage.removeItem(key);
394
+ } catch (error) {
395
+ console.warn("Could not clear the applet prefetch record.", error);
396
+ }
397
+ }
398
+ try {
399
+ var lastPrefetch = Number(window.sessionStorage.getItem(key));
400
+ if (lastPrefetch && Date.now() - lastPrefetch < 10 * 60 * 1000) return;
401
+ window.sessionStorage.setItem(key, String(Date.now()));
402
+ } catch (error) {
403
+ console.warn("Could not record the applet prefetch.", error);
404
+ }
405
+
406
+ fetch("/" + encodeURIComponent(code), {
407
+ credentials: "same-origin",
408
+ headers: { accept: "text/html" }
409
+ }).then(function (response) {
410
+ if (!response.ok) {
411
+ clearPrefetchRecord();
412
+ console.warn("Could not prefetch the applet document: " + response.status);
413
+ return null;
414
+ }
415
+ return response.text();
416
+ }).then(function (html) {
417
+ if (html === null) return;
418
+ var parsed = new DOMParser().parseFromString(html, "text/html");
419
+ parsed.querySelectorAll('script[src], link[rel="stylesheet"][href], link[rel="modulepreload"][href]').forEach(function (element) {
420
+ var source = element.getAttribute(element.tagName === "SCRIPT" ? "src" : "href");
421
+ var url = new URL(source, window.location.origin);
422
+ if (url.origin !== window.location.origin || !url.pathname.startsWith("/" + code + "/assets/")) return;
423
+ var link = document.createElement("link");
424
+ link.rel = "prefetch";
425
+ link.href = url.href;
426
+ if (element.crossOrigin !== null) link.crossOrigin = element.crossOrigin;
427
+ document.head.appendChild(link);
428
+ });
429
+ }).catch(function (error) {
430
+ clearPrefetchRecord();
431
+ console.warn("Could not prefetch the applet assets.", error);
432
+ });
433
+ }
434
+
352
435
  var params = new URLSearchParams(window.location.search);
353
436
  var theme = params.get("_hitch_theme");
354
437
  var dark = theme === "dark" ||
@@ -375,66 +458,96 @@ function clientBootstrapScript(parentOrigin, appletCode, cacheQueries) {
375
458
  window.addEventListener("popstate", reportLocation);
376
459
  window.addEventListener("hashchange", reportLocation);
377
460
 
378
- window.addEventListener("message", function (event) {
379
- if (
380
- event.source !== window.parent ||
381
- (parentOrigin !== null && event.origin !== parentOrigin) ||
382
- !event.data
383
- ) return;
384
-
461
+ function handleParentMessage(data) {
385
462
  if (
386
463
  parentOrigin !== null &&
387
- event.data.type === "hitch:location-request"
464
+ data.type === "hitch:location-request"
388
465
  ) {
389
466
  reportLocation();
390
467
  return;
391
468
  }
392
469
 
393
470
  if (
394
- event.data.type === "hitch:theme" &&
395
- (event.data.theme === "light" || event.data.theme === "dark")
471
+ data.type === "hitch:theme" &&
472
+ (data.theme === "light" || data.theme === "dark")
396
473
  ) {
397
- applyTheme(event.data.theme);
474
+ applyTheme(data.theme);
398
475
  return;
399
476
  }
400
477
 
401
478
  if (
402
479
  parentOrigin !== null &&
403
- event.data.type === "hitch:navigation-probe" &&
404
- typeof event.data.requestId === "string"
480
+ data.type === "hitch:navigation-probe" &&
481
+ typeof data.requestId === "string"
405
482
  ) {
406
483
  window.parent.postMessage(
407
- { type: "hitch:navigation-ready", requestId: event.data.requestId },
484
+ { type: "hitch:navigation-ready", requestId: data.requestId },
408
485
  parentOrigin
409
486
  );
410
487
  return;
411
488
  }
412
489
 
490
+ if (
491
+ parentOrigin !== null &&
492
+ data.type === "hitch:prefetch" &&
493
+ typeof data.appletCode === "string" &&
494
+ appletCodePattern.test(data.appletCode) &&
495
+ data.appletCode !== appletCode
496
+ ) {
497
+ prefetchAppletAssets(data.appletCode);
498
+ return;
499
+ }
500
+
413
501
  if (
414
502
  parentOrigin === null ||
415
- event.data.type !== "hitch:navigate" ||
416
- typeof event.data.appletCode !== "string" ||
417
- !appletCodePattern.test(event.data.appletCode) ||
418
- (event.data.theme !== "light" && event.data.theme !== "dark")
503
+ data.type !== "hitch:navigate" ||
504
+ typeof data.appletCode !== "string" ||
505
+ !appletCodePattern.test(data.appletCode) ||
506
+ (data.theme !== "light" && data.theme !== "dark")
419
507
  ) return;
420
508
 
421
- var nextHref = typeof event.data.href === "string"
422
- ? event.data.href
423
- : "/" + event.data.appletCode;
509
+ var nextHref = typeof data.href === "string"
510
+ ? data.href
511
+ : "/" + data.appletCode;
424
512
  var nextUrl = new URL(nextHref, window.location.origin);
425
- var nextAppletPath = "/" + event.data.appletCode;
513
+ var nextAppletPath = "/" + data.appletCode;
426
514
  if (
427
515
  nextUrl.origin !== window.location.origin ||
428
516
  (nextUrl.pathname !== nextAppletPath && !nextUrl.pathname.startsWith(nextAppletPath + "/"))
429
517
  ) return;
430
518
 
431
- applyTheme(event.data.theme);
519
+ applyTheme(data.theme);
432
520
  if (
433
521
  nextUrl.pathname + nextUrl.search + nextUrl.hash ===
434
522
  window.location.pathname + window.location.search + window.location.hash
435
523
  ) return;
436
- nextUrl.searchParams.set("_hitch_theme", event.data.theme);
524
+ nextUrl.searchParams.set("_hitch_theme", data.theme);
437
525
  window.location.replace(nextUrl);
526
+ }
527
+
528
+ window.addEventListener("message", function (event) {
529
+ if (
530
+ event.source !== window.parent ||
531
+ (parentOrigin !== null && event.origin !== parentOrigin) ||
532
+ !event.data
533
+ ) return;
534
+
535
+ // A port message arrives at once; a window message waits for the
536
+ // dashboard's current task, which is often the render of a navigation.
537
+ if (parentOrigin !== null && event.data.type === "hitch:connect" && event.ports[0]) {
538
+ var port = event.ports[0];
539
+ // The port dies with this document, so tell the dashboard.
540
+ window.addEventListener("pagehide", function () {
541
+ port.postMessage({ type: "hitch:disconnect" });
542
+ });
543
+ port.onmessage = function (portEvent) {
544
+ if (portEvent.data) handleParentMessage(portEvent.data);
545
+ };
546
+ port.postMessage({ type: "hitch:navigation-ready" });
547
+ return;
548
+ }
549
+
550
+ handleParentMessage(event.data);
438
551
  });
439
552
  })();`;
440
553
  }
@@ -451,6 +564,7 @@ function applet(sourceDefinition) {
451
564
  let buildRootOutDir;
452
565
  let clearedOutDir = false;
453
566
  let contractSync;
567
+ let buildRoutes = false;
454
568
  let themeStylesheetUrl;
455
569
  let devApiUrl;
456
570
  let devListener;
@@ -521,6 +635,7 @@ function applet(sourceDefinition) {
521
635
  return apiManifest;
522
636
  } },
523
637
  async config(config, environment) {
638
+ buildRoutes = environment.command === "build" && environment.mode !== "test";
524
639
  const { root, manifest, hasClient, hasAuthorRoot, hasServer, hasRouter } = resolveAppletConfig(config.root ?? resolve("."));
525
640
  apiManifest = manifest;
526
641
  buildRootOutDir = resolve(root, config.build?.outDir ?? "dist");
@@ -624,6 +739,17 @@ function applet(sourceDefinition) {
624
739
  configResolved(config) {
625
740
  apiManifest = resolveAppletConfig(config.root).manifest;
626
741
  },
742
+ async load(id) {
743
+ if (!buildRoutes) return;
744
+ const { root, manifest } = requireAppletConfig();
745
+ if (id !== normalizePath(resolve(root, ".hitch/routes.gen.ts"))) return;
746
+ const { contract } = await import(`${pathToFileURL(id).href}?${randomUUID()}`);
747
+ const { batch, ...records } = contract;
748
+ return routesModuleSource({
749
+ ...pruneContract({ records }, manifest).records,
750
+ ...Object.hasOwn(contract, "batch") ? { batch } : {}
751
+ });
752
+ },
627
753
  configureServer(server) {
628
754
  if (devContext === void 0) return;
629
755
  const { manifest } = requireAppletConfig();
@@ -797,12 +923,16 @@ function applet(sourceDefinition) {
797
923
  ...clsxFromCn,
798
924
  enforce: "pre"
799
925
  },
800
- ...hasClientSource ? [tanstackRouter({
801
- generatedRouteTree: ".hitch/routeTree.gen.ts",
802
- routesDirectory: "src/client/routes",
803
- routeFileIgnorePattern: "__root",
804
- virtualRouteConfig
805
- }), react({ compiler: true })] : [],
926
+ ...hasClientSource ? [
927
+ tanstackRouter({
928
+ generatedRouteTree: ".hitch/routeTree.gen.ts",
929
+ routesDirectory: "src/client/routes",
930
+ routeFileIgnorePattern: "__root",
931
+ virtualRouteConfig
932
+ }),
933
+ react({ compiler: true }),
934
+ optimizeLocales.vite({ locales: ["en-US"] })
935
+ ] : [],
806
936
  cloudflarePlugins
807
937
  ];
808
938
  }
@@ -0,0 +1,31 @@
1
+ import { createRequire } from "node:module";
2
+ import { existsSync } from "node:fs";
3
+ import { basename, dirname, resolve } from "node:path";
4
+ //#region ../shared/auth/src/workspace.mts
5
+ const ORG_CODE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
6
+ const require = createRequire(import.meta.url);
7
+ function readWorkspaceConfig(dir) {
8
+ const path = resolve(dir, "hitch.config.ts");
9
+ if (!existsSync(path)) return void 0;
10
+ let module;
11
+ try {
12
+ module = require(path);
13
+ } catch (error) {
14
+ throw new Error(`Failed to load ${path}.`, { cause: error });
15
+ }
16
+ const config = module !== null && typeof module === "object" && "default" in module ? module.default : module;
17
+ if (config === null || typeof config !== "object" || !("sdk" in config) || typeof config.sdk !== "string" || config.sdk.length === 0 || !("org" in config) || typeof config.org !== "string" || !ORG_CODE_PATTERN.test(config.org)) throw new Error(`Invalid Hitch config in ${path}: "sdk" must be a package spec and "org" an organization code.`);
18
+ const githubWorkflow = "githubWorkflow" in config ? config.githubWorkflow : void 0;
19
+ if (githubWorkflow !== void 0 && typeof githubWorkflow !== "boolean") throw new Error(`Invalid Hitch config in ${path}: "githubWorkflow" must be a boolean.`);
20
+ return {
21
+ sdk: config.sdk,
22
+ org: config.org,
23
+ githubWorkflow
24
+ };
25
+ }
26
+ function findWorkspaceConfig(cwd) {
27
+ const appletsDir = dirname(cwd);
28
+ return readWorkspaceConfig(cwd) ?? (basename(appletsDir) === "applets" ? readWorkspaceConfig(dirname(appletsDir)) : void 0);
29
+ }
30
+ //#endregion
31
+ export { readWorkspaceConfig as n, findWorkspaceConfig as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hitch42/applet",
3
- "version": "0.1.1-beta.15",
3
+ "version": "0.1.1-beta.16",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "applet": "./dist/applet.js"
@@ -11,6 +11,9 @@
11
11
  "scaffold"
12
12
  ],
13
13
  "type": "module",
14
+ "sideEffects": [
15
+ "*.css"
16
+ ],
14
17
  "exports": {
15
18
  ".": {
16
19
  "source": "./src/index.ts",
@@ -28,6 +31,14 @@
28
31
  "source": "./src/react/index.ts",
29
32
  "default": "./dist/react.js"
30
33
  },
34
+ "./react/markdown": {
35
+ "source": "./src/react/markdown.tsx",
36
+ "default": "./dist/react/markdown.js"
37
+ },
38
+ "./react/markdown-editor": {
39
+ "source": "./src/react/markdown-editor.tsx",
40
+ "default": "./dist/react/markdown-editor.js"
41
+ },
31
42
  "./telephony": {
32
43
  "source": "./src/telephony/index.ts",
33
44
  "default": "./dist/telephony.js"
@@ -59,6 +70,7 @@
59
70
  "@orpc/contract": "^1.15.0",
60
71
  "@orpc/openapi-client": "^1.15.0",
61
72
  "@orpc/tanstack-query": "^1.15.0",
73
+ "@react-aria/optimize-locales-plugin": "^2.1.0",
62
74
  "@tailwindcss/vite": "^4.3.0",
63
75
  "@tanstack/react-query-persist-client": "5.101.4",
64
76
  "@tanstack/router-cli": "^1.167.24",
@@ -10,7 +10,7 @@ The editor supports GFM tables and task lists. A plain table serializes as GFM.
10
10
 
11
11
  ```tsx
12
12
  import { hitch } from "@hitch42/applet/client";
13
- import { MarkdownEditor } from "@hitch42/applet/react";
13
+ import { MarkdownEditor } from "@hitch42/applet/react/markdown-editor";
14
14
 
15
15
  <MarkdownEditor value={value} onChange={setValue} files={hitch.files} />;
16
16
  ```
@@ -23,13 +23,31 @@ The `files` value must supply both `upload(file)` and `download(id)`. The editor
23
23
 
24
24
  While an upload runs, the editor shows the image from a temporary local URL and blocks native form submission, so a form cannot save a half-done upload. A failed upload removes the image and calls `onUploadError` if you pass one; without it, the editor logs the error and continues with the next file.
25
25
 
26
+ ## Load the editor lazily
27
+
28
+ The editor uses Tiptap and is large (about 166 KB gzip). Load it lazily when it is not on the first screen, such as in a dialog or an edit form.
29
+
30
+ ```tsx
31
+ import { lazy, Suspense } from "react";
32
+
33
+ const MarkdownEditor = lazy(() =>
34
+ import("@hitch42/applet/react/markdown-editor").then((m) => ({ default: m.MarkdownEditor })),
35
+ );
36
+
37
+ <Suspense fallback={<div className="min-h-32" />}>
38
+ <MarkdownEditor value={value} onChange={setValue} files={hitch.files} />
39
+ </Suspense>;
40
+ ```
41
+
42
+ Use the `/react/markdown-editor` path for the lazy import. A lazy import from `@hitch42/applet/react` does not split the editor out when the applet also imports `Markdown`.
43
+
26
44
  ## Render markdown
27
45
 
28
46
  Pass the same files surface to `Markdown`:
29
47
 
30
48
  ```tsx
31
49
  import { hitch } from "@hitch42/applet/client";
32
- import { Markdown } from "@hitch42/applet/react";
50
+ import { Markdown } from "@hitch42/applet/react/markdown";
33
51
 
34
52
  <Markdown files={hitch.files}>{value}</Markdown>;
35
53
  ```
@@ -1,2 +0,0 @@
1
- import { i as runBuild } from "./build-BkhBHdMs.js";
2
- export { runBuild };