@makershq/react 0.0.0-stage → 0.3.1

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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -2
  3. package/THIRD_PARTY_NOTICES.md +77 -0
  4. package/dist/api.d.ts +82 -0
  5. package/dist/api.js +183 -0
  6. package/dist/cache.d.ts +22 -0
  7. package/dist/cache.js +86 -0
  8. package/dist/components/icons/external-link/index.d.ts +52 -0
  9. package/dist/components/icons/external-link/index.js +60 -0
  10. package/dist/components/icons/index.d.ts +31 -0
  11. package/dist/components/icons/index.js +56 -0
  12. package/dist/components/icons/reduced-motion.d.ts +4 -0
  13. package/dist/components/icons/reduced-motion.js +24 -0
  14. package/dist/components/icons/x/index.d.ts +98 -0
  15. package/dist/components/icons/x/index.js +87 -0
  16. package/dist/components/release-markdown/index.d.ts +22 -0
  17. package/dist/components/release-markdown/index.js +101 -0
  18. package/dist/components/release-notes/index.d.ts +51 -0
  19. package/dist/components/release-notes/index.js +89 -0
  20. package/dist/connectors/use-release-note-list-connector.d.ts +24 -0
  21. package/dist/connectors/use-release-note-list-connector.js +219 -0
  22. package/dist/index.d.ts +20 -0
  23. package/dist/index.js +13 -0
  24. package/dist/styles.css +2 -0
  25. package/dist/styles.d.ts +8 -0
  26. package/dist/tauri/components/native-child/index.d.ts +19 -0
  27. package/dist/tauri/components/native-child/index.js +146 -0
  28. package/dist/tauri/components/native-child/theme.d.ts +12 -0
  29. package/dist/tauri/components/native-child/theme.js +45 -0
  30. package/dist/tauri/components/release-notes-host/index.d.ts +36 -0
  31. package/dist/tauri/components/release-notes-host/index.js +81 -0
  32. package/dist/tauri/components/release-notes-settings/index.d.ts +10 -0
  33. package/dist/tauri/components/release-notes-settings/index.js +23 -0
  34. package/dist/tauri/components/update-window/index.d.ts +20 -0
  35. package/dist/tauri/components/update-window/index.js +68 -0
  36. package/dist/tauri/context.d.ts +20 -0
  37. package/dist/tauri/context.js +22 -0
  38. package/dist/tauri/contracts.d.ts +84 -0
  39. package/dist/tauri/contracts.js +7 -0
  40. package/dist/tauri/hooks/use-release-notes.d.ts +92 -0
  41. package/dist/tauri/hooks/use-release-notes.js +101 -0
  42. package/dist/tauri/host-controller.d.ts +134 -0
  43. package/dist/tauri/host-controller.js +951 -0
  44. package/dist/tauri/index.d.ts +10 -0
  45. package/dist/tauri/index.js +9 -0
  46. package/dist/tauri/peer-protocol.d.ts +36 -0
  47. package/dist/tauri/peer-protocol.js +43 -0
  48. package/dist/tauri/preferences.d.ts +18 -0
  49. package/dist/tauri/preferences.js +39 -0
  50. package/dist/tauri/protocol.d.ts +71 -0
  51. package/dist/tauri/protocol.js +106 -0
  52. package/dist/tauri/remote-settings-client.d.ts +72 -0
  53. package/dist/tauri/remote-settings-client.js +265 -0
  54. package/dist/website/components/update-toast/index.d.ts +14 -0
  55. package/dist/website/components/update-toast/index.js +22 -0
  56. package/dist/website/components/website-updates/index.d.ts +24 -0
  57. package/dist/website/components/website-updates/index.js +35 -0
  58. package/dist/website/components/whats-new-panel/index.d.ts +23 -0
  59. package/dist/website/components/whats-new-panel/index.js +38 -0
  60. package/dist/website/preferences.d.ts +32 -0
  61. package/dist/website/preferences.js +85 -0
  62. package/dist/website/use-website-updates-connector.d.ts +34 -0
  63. package/dist/website/use-website-updates-connector.js +318 -0
  64. package/dist/website/version.d.ts +27 -0
  65. package/dist/website/version.js +73 -0
  66. package/package.json +71 -3
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Coordinates deployment checks and exact notes for the JavaScript already loaded.
3
+ *
4
+ * Owns browser triggers, fresh publication requests, latched presentation and save-before-reload.
5
+ *
6
+ * Share this lifecycle with default and app-owned presentation; apps own their refresh save barrier.
7
+ */
8
+ import { useCallback, useEffect, useRef, useState } from 'react';
9
+ import { gt } from 'semver';
10
+ import { fetchReleasePage } from '../api.js';
11
+ import { acknowledgeWebsiteVersion, dismissWebsiteBuild, readDismissedWebsiteBuild, readWebsitePreferences, websitePreferenceKey, } from './preferences.js';
12
+ import { fetchWebsiteBuild, reloadWebsite, websiteManifestUrl, websiteReleaseVersion, } from './version.js';
13
+ /** Coordinates deployment detection, fresh exact notes and safe refresh.
14
+ * @param options - Loaded build, release project and refresh configuration.
15
+ * @param options.projectId - Public project identifier.
16
+ * @param options.currentBuild - Build compiled into the running JavaScript.
17
+ * @param options.currentBuild.buildId - Opaque loaded deployment identity.
18
+ * @param options.currentBuild.releaseVersion - Optional loaded semantic release version.
19
+ * @param options.manifestUrl - Deployment manifest location.
20
+ * @param options.beforeRefresh - Consumer save barrier before reloading.
21
+ * @returns Visible release/candidate and actions bound to that presentation.
22
+ */
23
+ export function useWebsiteUpdatesConnector({ projectId, currentBuild, manifestUrl = '/version.json', beforeRefresh, }) {
24
+ const version = websiteReleaseVersion(currentBuild.releaseVersion);
25
+ const identity = JSON.stringify([
26
+ projectId,
27
+ currentBuild.buildId,
28
+ version,
29
+ manifestUrl,
30
+ ]);
31
+ const [state, setState] = useState({
32
+ identity,
33
+ candidate: null,
34
+ displayedRelease: null,
35
+ refreshing: false,
36
+ error: null,
37
+ });
38
+ const runtime = useRef(null);
39
+ const activeIdentity = useRef(identity);
40
+ activeIdentity.current = identity;
41
+ const save = useRef(beforeRefresh);
42
+ save.current = beforeRefresh;
43
+ useEffect(() => {
44
+ let disposed = false;
45
+ let fetching = false;
46
+ let refreshing = false;
47
+ let displayedRelease = null;
48
+ let candidate = null;
49
+ let dismissedBuild = null;
50
+ let acknowledgedVersion = null;
51
+ let key = null;
52
+ let url = null;
53
+ let lastVisibilityCheck = Date.now();
54
+ let pendingCheck = null;
55
+ let pendingDeadline;
56
+ const update = (patch) => {
57
+ if (!disposed)
58
+ setState((previous) => ({
59
+ ...(previous.identity === identity
60
+ ? previous
61
+ : {
62
+ identity,
63
+ candidate: null,
64
+ displayedRelease: null,
65
+ refreshing: false,
66
+ error: null,
67
+ }),
68
+ ...patch,
69
+ }));
70
+ };
71
+ try {
72
+ url = websiteManifestUrl(manifestUrl, window.location.href);
73
+ key = websitePreferenceKey(projectId, url);
74
+ dismissedBuild = readDismissedWebsiteBuild(key);
75
+ acknowledgedVersion =
76
+ readWebsitePreferences(key, version)?.acknowledgedVersion ?? null;
77
+ }
78
+ catch {
79
+ /* Invalid manifest URLs are silent; no requests or automatic panels. */
80
+ }
81
+ update({
82
+ candidate: null,
83
+ displayedRelease: null,
84
+ refreshing: false,
85
+ error: null,
86
+ });
87
+ const owner = {
88
+ identity,
89
+ async refresh(build) {
90
+ if (disposed ||
91
+ activeIdentity.current !== identity ||
92
+ refreshing ||
93
+ !candidate ||
94
+ candidate !== build ||
95
+ candidate.buildId === dismissedBuild ||
96
+ displayedRelease)
97
+ return;
98
+ refreshing = true;
99
+ update({ refreshing: true, error: null });
100
+ try {
101
+ await save.current?.();
102
+ if (!disposed &&
103
+ activeIdentity.current === identity &&
104
+ runtime.current === owner &&
105
+ candidate === build &&
106
+ candidate.buildId !== dismissedBuild &&
107
+ !displayedRelease)
108
+ reloadWebsite();
109
+ else {
110
+ refreshing = false;
111
+ update({ refreshing: false });
112
+ }
113
+ }
114
+ catch {
115
+ refreshing = false;
116
+ update({
117
+ ...(candidate === build && candidate.buildId !== dismissedBuild
118
+ ? { error: 'Couldn’t refresh. Please try again.' }
119
+ : {}),
120
+ refreshing: false,
121
+ });
122
+ }
123
+ },
124
+ dismissCandidate(build) {
125
+ if (disposed || candidate !== build)
126
+ return;
127
+ dismissedBuild = build.buildId;
128
+ if (key)
129
+ dismissWebsiteBuild(key, build.buildId);
130
+ update({
131
+ candidate: null,
132
+ error: null,
133
+ });
134
+ },
135
+ dismissRelease(release) {
136
+ if (disposed || displayedRelease !== release)
137
+ return;
138
+ displayedRelease = null;
139
+ update({
140
+ displayedRelease: null,
141
+ candidate: candidate?.buildId === dismissedBuild ? null : candidate,
142
+ });
143
+ },
144
+ renderedRelease(release) {
145
+ if (!disposed &&
146
+ key &&
147
+ release === displayedRelease &&
148
+ release.version === version)
149
+ acknowledgeWebsiteVersion(key, version);
150
+ },
151
+ };
152
+ runtime.current = owner;
153
+ const readPreferences = () => {
154
+ if (!key)
155
+ return null;
156
+ const preferences = readWebsitePreferences(key, version);
157
+ if (!preferences)
158
+ return null;
159
+ if (preferences.acknowledgedVersion &&
160
+ (!acknowledgedVersion ||
161
+ gt(preferences.acknowledgedVersion, acknowledgedVersion)))
162
+ acknowledgedVersion = preferences.acknowledgedVersion;
163
+ return { acknowledgedVersion };
164
+ };
165
+ const onStorage = (event) => {
166
+ if (event.key === key || event.key === null)
167
+ readPreferences();
168
+ };
169
+ const check = async () => {
170
+ if (disposed || fetching || !url || !key)
171
+ return;
172
+ fetching = true;
173
+ const request = new AbortController();
174
+ pendingCheck = request;
175
+ const cancelled = new Promise((resolve) => {
176
+ request.signal.addEventListener('abort', () => resolve(), {
177
+ once: true,
178
+ });
179
+ });
180
+ const deadline = window.setTimeout(() => request.abort(), 30000);
181
+ pendingDeadline = deadline;
182
+ const preferences = readPreferences();
183
+ const showNotes = !displayedRelease &&
184
+ version &&
185
+ preferences &&
186
+ (!preferences.acknowledgedVersion ||
187
+ gt(version, preferences.acknowledgedVersion));
188
+ try {
189
+ await Promise.race([
190
+ Promise.allSettled([
191
+ (async () => {
192
+ const deployed = await fetchWebsiteBuild(url, request.signal);
193
+ if (disposed ||
194
+ request.signal.aborted ||
195
+ pendingCheck !== request)
196
+ return;
197
+ candidate =
198
+ deployed.buildId === currentBuild.buildId
199
+ ? null
200
+ : candidate?.buildId === deployed.buildId
201
+ ? candidate
202
+ : deployed;
203
+ update({
204
+ candidate: candidate?.buildId === dismissedBuild ? null : candidate,
205
+ });
206
+ })(),
207
+ ...(showNotes
208
+ ? [
209
+ (async () => {
210
+ const page = await fetchReleasePage(projectId, {
211
+ version,
212
+ signal: request.signal,
213
+ });
214
+ if (disposed ||
215
+ request.signal.aborted ||
216
+ pendingCheck !== request ||
217
+ displayedRelease ||
218
+ page.items.length !== 1)
219
+ return;
220
+ const latest = readPreferences();
221
+ if (!latest ||
222
+ (latest.acknowledgedVersion &&
223
+ !gt(version, latest.acknowledgedVersion)))
224
+ return;
225
+ displayedRelease = page.items[0];
226
+ update({ displayedRelease });
227
+ })(),
228
+ ]
229
+ : []),
230
+ ]),
231
+ cancelled,
232
+ ]);
233
+ }
234
+ finally {
235
+ window.clearTimeout(deadline);
236
+ request.abort();
237
+ if (pendingCheck === request) {
238
+ pendingCheck = null;
239
+ pendingDeadline = undefined;
240
+ fetching = false;
241
+ }
242
+ }
243
+ };
244
+ const onVisible = () => {
245
+ if (document.visibilityState !== 'visible' ||
246
+ Date.now() - lastVisibilityCheck < 60000)
247
+ return;
248
+ lastVisibilityCheck = Date.now();
249
+ void check();
250
+ };
251
+ const onOnline = () => {
252
+ void check();
253
+ };
254
+ const interval = window.setInterval(() => {
255
+ if (document.visibilityState === 'visible')
256
+ void check();
257
+ }, 300000);
258
+ window.addEventListener('focus', onVisible);
259
+ document.addEventListener('visibilitychange', onVisible);
260
+ window.addEventListener('online', onOnline);
261
+ window.addEventListener('storage', onStorage);
262
+ void check();
263
+ return () => {
264
+ disposed = true;
265
+ pendingCheck?.abort();
266
+ window.clearTimeout(pendingDeadline);
267
+ window.clearInterval(interval);
268
+ window.removeEventListener('focus', onVisible);
269
+ document.removeEventListener('visibilitychange', onVisible);
270
+ window.removeEventListener('online', onOnline);
271
+ window.removeEventListener('storage', onStorage);
272
+ if (runtime.current === owner)
273
+ runtime.current = null;
274
+ };
275
+ }, [identity, projectId, currentBuild.buildId, version, manifestUrl]);
276
+ const active = state.identity === identity
277
+ ? state
278
+ : {
279
+ candidate: null,
280
+ displayedRelease: null,
281
+ refreshing: false,
282
+ error: null,
283
+ };
284
+ const build = active.candidate;
285
+ const release = active.displayedRelease;
286
+ const owner = runtime.current;
287
+ const current = useCallback(() => activeIdentity.current === identity &&
288
+ owner?.identity === identity &&
289
+ runtime.current === owner
290
+ ? runtime.current
291
+ : null, [identity, owner]);
292
+ const refresh = useCallback(async () => {
293
+ if (build)
294
+ await current()?.refresh(build);
295
+ }, [current, build]);
296
+ const dismissCandidate = useCallback(() => {
297
+ if (build)
298
+ current()?.dismissCandidate(build);
299
+ }, [current, build]);
300
+ const dismissRelease = useCallback(() => {
301
+ if (release)
302
+ current()?.dismissRelease(release);
303
+ }, [current, release]);
304
+ const onReleaseRendered = useCallback(() => {
305
+ if (release)
306
+ current()?.renderedRelease(release);
307
+ }, [current, release]);
308
+ return {
309
+ candidate: active.candidate,
310
+ displayedRelease: release,
311
+ refreshing: active.refreshing,
312
+ error: active.error,
313
+ refresh,
314
+ dismissCandidate,
315
+ dismissRelease,
316
+ onReleaseRendered,
317
+ };
318
+ }
@@ -0,0 +1,27 @@
1
+ /** One compiled or deployed website build. */
2
+ export interface WebsiteBuild {
3
+ buildId: string;
4
+ /** Opaque deployment identifier, including rollbacks. */
5
+ releaseVersion?: string;
6
+ }
7
+ /** Normalizes a loaded application version without making an invalid version fatal.
8
+ * @param value - Optional compiled release version.
9
+ * @returns A canonical semantic version, or null when absent or invalid.
10
+ */
11
+ export declare function websiteReleaseVersion(value?: string): string | null;
12
+ /** Resolves a manifest against the document URL without sending credentials.
13
+ * @param value - Consumer manifest URL.
14
+ * @param base - Browser document URL.
15
+ * @returns An HTTP(S) manifest URL with no fragment or embedded credentials.
16
+ * @throws Error when the URL is not an HTTP(S) destination.
17
+ */
18
+ export declare function websiteManifestUrl(value: string, base: string): string;
19
+ /** Requests a fresh deployment identity.
20
+ * @param url - Resolved manifest URL.
21
+ * @param signal - Lifecycle cancellation signal.
22
+ * @returns A validated deployed build, without interpreting its release version.
23
+ * @throws Error for unavailable or malformed manifests.
24
+ */
25
+ export declare function fetchWebsiteBuild(url: string, signal: AbortSignal): Promise<WebsiteBuild>;
26
+ /** Reloads the consumer document after its own save barrier completes. */
27
+ export declare function reloadWebsite(): void;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Reads a website deployment manifest without relying on browser globals at import time.
3
+ *
4
+ * Owns opaque build identity validation and strict optional release-version normalization.
5
+ *
6
+ * Keep deployment detection separate from the version of the JavaScript already loaded.
7
+ */
8
+ import { normalizeReleaseVersion } from '../api.js';
9
+ /** Normalizes a loaded application version without making an invalid version fatal.
10
+ * @param value - Optional compiled release version.
11
+ * @returns A canonical semantic version, or null when absent or invalid.
12
+ */
13
+ export function websiteReleaseVersion(value) {
14
+ try {
15
+ return value ? normalizeReleaseVersion(value) : null;
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ /** Resolves a manifest against the document URL without sending credentials.
22
+ * @param value - Consumer manifest URL.
23
+ * @param base - Browser document URL.
24
+ * @returns An HTTP(S) manifest URL with no fragment or embedded credentials.
25
+ * @throws Error when the URL is not an HTTP(S) destination.
26
+ */
27
+ export function websiteManifestUrl(value, base) {
28
+ const url = new URL(value, base);
29
+ if (!['http:', 'https:'].includes(url.protocol) ||
30
+ url.username ||
31
+ url.password)
32
+ throw new Error('A valid website manifest URL is required.');
33
+ url.hash = '';
34
+ return url.href;
35
+ }
36
+ /** Requests a fresh deployment identity.
37
+ * @param url - Resolved manifest URL.
38
+ * @param signal - Lifecycle cancellation signal.
39
+ * @returns A validated deployed build, without interpreting its release version.
40
+ * @throws Error for unavailable or malformed manifests.
41
+ */
42
+ export async function fetchWebsiteBuild(url, signal) {
43
+ const response = await fetch(url, {
44
+ cache: 'no-store',
45
+ credentials: 'omit',
46
+ signal,
47
+ headers: { Accept: 'application/json' },
48
+ });
49
+ if (!response.ok)
50
+ throw new Error('Website version is unavailable.');
51
+ const body = await response.text();
52
+ if (body.length > 16384)
53
+ throw new Error('Invalid website version.');
54
+ const value = JSON.parse(body);
55
+ if (!value ||
56
+ typeof value.buildId !== 'string' ||
57
+ !value.buildId.trim() ||
58
+ value.buildId.length > 512 ||
59
+ (value.releaseVersion !== undefined &&
60
+ (typeof value.releaseVersion !== 'string' ||
61
+ value.releaseVersion.length > 256)))
62
+ throw new Error('Invalid website version.');
63
+ return {
64
+ buildId: value.buildId,
65
+ ...(value.releaseVersion === undefined
66
+ ? {}
67
+ : { releaseVersion: value.releaseVersion }),
68
+ };
69
+ }
70
+ /** Reloads the consumer document after its own save barrier completes. */
71
+ export function reloadWebsite() {
72
+ window.location.reload();
73
+ }
package/package.json CHANGED
@@ -1,6 +1,74 @@
1
1
  {
2
2
  "name": "@makershq/react",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.3.1",
4
+ "description": "Release notes for React and optional Tauri 2 apps",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "LICENSE",
11
+ "THIRD_PARTY_NOTICES.md"
12
+ ],
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ },
20
+ "./tauri": {
21
+ "types": "./dist/tauri/index.d.ts",
22
+ "import": "./dist/tauri/index.js"
23
+ },
24
+ "./styles.css": {
25
+ "types": "./dist/styles.d.ts",
26
+ "default": "./dist/styles.css"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "sideEffects": [
31
+ "**/*.css"
32
+ ],
33
+ "dependencies": {
34
+ "@animate-ui/react": "2.2.1",
35
+ "motion": "14.6.0",
36
+ "react-markdown": "^10.1.0",
37
+ "remark-gfm": "^4.0.1",
38
+ "rehype-raw": "^7.0.0",
39
+ "rehype-sanitize": "^6.0.0",
40
+ "semver": "^7.7.3"
41
+ },
42
+ "peerDependencies": {
43
+ "react": "^19.0.0",
44
+ "react-dom": "^19.0.0",
45
+ "@tauri-apps/api": "^2.0.0",
46
+ "@tauri-apps/plugin-store": "^2.0.0",
47
+ "@tauri-apps/plugin-opener": "^2.0.0"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "@tauri-apps/api": {
51
+ "optional": true
52
+ },
53
+ "@tauri-apps/plugin-store": {
54
+ "optional": true
55
+ },
56
+ "@tauri-apps/plugin-opener": {
57
+ "optional": true
58
+ }
59
+ },
60
+ "devDependencies": {
61
+ "@tailwindcss/cli": "^4.0.0",
62
+ "@tauri-apps/api": "^2.0.0",
63
+ "@tauri-apps/plugin-store": "^2.0.0",
64
+ "@tauri-apps/plugin-opener": "^2.0.0",
65
+ "@types/semver": "^7.7.1",
66
+ "typescript-5-8": "npm:typescript@~5.8.3"
67
+ },
68
+ "scripts": {
69
+ "build": "node scripts/build.mjs",
70
+ "typecheck": "tsc --noEmit",
71
+ "test": "vitest run --config vitest.config.ts",
72
+ "test:pack": "node scripts/pack-test.mjs"
73
+ }
6
74
  }