@olonjs/cli 3.0.82 → 3.0.83

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.
@@ -596,7 +596,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
596
596
  "@tiptap/extension-link": "^2.11.5",
597
597
  "@tiptap/react": "^2.11.5",
598
598
  "@tiptap/starter-kit": "^2.11.5",
599
- "@olonjs/core": "^1.0.70",
599
+ "@olonjs/core": "^1.0.71",
600
600
  "clsx": "^2.1.1",
601
601
  "lucide-react": "^0.474.0",
602
602
  "react": "^19.0.0",
@@ -941,6 +941,7 @@ import themeData from '@/data/config/theme.json';
941
941
  import menuData from '@/data/config/menu.json';
942
942
  import { getFilePages } from '@/lib/getFilePages';
943
943
  import { DopaDrawer } from '@/components/save-drawer/DopaDrawer';
944
+ import { Skeleton } from '@/components/ui/skeleton';
944
945
 
945
946
  import tenantCss from './index.css?inline';
946
947
 
@@ -1194,6 +1195,16 @@ function cloudFingerprint(apiBase: string, apiKey: string): string {
1194
1195
  return `${normalizeApiBase(apiBase)}::${apiKey.slice(-8)}`;
1195
1196
  }
1196
1197
 
1198
+ function normalizeSlugForCache(slug: string): string {
1199
+ return (
1200
+ slug
1201
+ .trim()
1202
+ .toLowerCase()
1203
+ .replace(/[^a-z0-9/_-]/g, '-')
1204
+ .replace(/^\/+|\/+$/g, '') || 'home'
1205
+ );
1206
+ }
1207
+
1197
1208
  function readCachedCloudContent(fingerprint: string): CachedCloudContent | null {
1198
1209
  try {
1199
1210
  const raw = localStorage.getItem(CLOUD_CACHE_KEY);
@@ -1248,7 +1259,6 @@ function App() {
1248
1259
  const activeCloudSaveController = useRef<AbortController | null>(null);
1249
1260
  const contentLoadInFlight = useRef<Promise<void> | null>(null);
1250
1261
  const pendingCloudSave = useRef<{ state: ProjectState; slug: string } | null>(null);
1251
- const warmBootFromCloudCache = useRef(false);
1252
1262
  const cloudApiCandidates = useMemo(
1253
1263
  () => (isCloudMode && CLOUD_API_URL ? buildApiCandidates(CLOUD_API_URL) : []),
1254
1264
  [isCloudMode, CLOUD_API_URL]
@@ -1288,26 +1298,16 @@ function App() {
1288
1298
  const primaryApiBase = cloudApiCandidates[0] ?? normalizeApiBase(CLOUD_API_URL);
1289
1299
  const fingerprint = cloudFingerprint(primaryApiBase, CLOUD_API_KEY);
1290
1300
  const cached = readCachedCloudContent(fingerprint);
1301
+ const cachedPages = cached ? toPagesRecord(cached.pages) : null;
1302
+ const cachedSite = cached ? coerceSiteConfig(cached.siteConfig) : null;
1303
+ const hasCachedFallback = Boolean((cachedPages && Object.keys(cachedPages).length > 0) || cachedSite);
1291
1304
  if (cached) {
1292
- const cachedPages = toPagesRecord(cached.pages);
1293
- const cachedSite = coerceSiteConfig(cached.siteConfig);
1294
- if (cachedPages && Object.keys(cachedPages).length > 0) {
1295
- setPages(cachedPages);
1296
- }
1297
- if (cachedSite) {
1298
- setSiteConfig(cachedSite);
1299
- }
1300
- setContentMode('cloud');
1301
- setContentFallback(null);
1302
- warmBootFromCloudCache.current = true;
1303
- setShowTopProgress(false);
1304
- setHasInitialCloudResolved(true);
1305
1305
  logBootstrapEvent('boot.cloud.cache_hit', { ageMs: Date.now() - cached.savedAt });
1306
- } else {
1307
- warmBootFromCloudCache.current = false;
1308
- setShowTopProgress(true);
1309
- setHasInitialCloudResolved(false);
1310
1306
  }
1307
+ setContentMode('cloud');
1308
+ setContentFallback(null);
1309
+ setShowTopProgress(true);
1310
+ setHasInitialCloudResolved(false);
1311
1311
  logBootstrapEvent('boot.start', { mode: 'cloud', apiCandidates: cloudApiCandidates.length });
1312
1312
 
1313
1313
  const loadCloudContent = async () => {
@@ -1320,6 +1320,7 @@ function App() {
1320
1320
  try {
1321
1321
  const res = await fetch(`${apiBase}/content`, {
1322
1322
  method: 'GET',
1323
+ cache: 'no-store',
1323
1324
  headers: {
1324
1325
  Authorization: `Bearer ${CLOUD_API_KEY}`,
1325
1326
  },
@@ -1422,7 +1423,13 @@ function App() {
1422
1423
  } catch (error: unknown) {
1423
1424
  if (controller.signal.aborted) return;
1424
1425
  const failure = toCloudLoadFailure(error);
1425
- if (warmBootFromCloudCache.current) {
1426
+ if (hasCachedFallback) {
1427
+ if (cachedPages && Object.keys(cachedPages).length > 0) {
1428
+ setPages(cachedPages);
1429
+ }
1430
+ if (cachedSite) {
1431
+ setSiteConfig(cachedSite);
1432
+ }
1426
1433
  setContentMode('cloud');
1427
1434
  setContentFallback({
1428
1435
  reasonCode: 'CLOUD_REFRESH_FAILED',
@@ -1589,14 +1596,26 @@ function App() {
1589
1596
  },
1590
1597
  body: JSON.stringify({
1591
1598
  slug,
1592
- type: 'page',
1593
- data: state.page,
1599
+ page: state.page,
1600
+ siteConfig: state.site,
1594
1601
  }),
1595
1602
  });
1596
1603
  const body = (await res.json().catch(() => ({}))) as { error?: string; code?: string };
1597
1604
  if (!res.ok) {
1598
1605
  throw new Error(body.error || body.code || `Hot save failed: ${res.status}`);
1599
1606
  }
1607
+ const keyFingerprint = cloudFingerprint(apiBase, CLOUD_API_KEY);
1608
+ const normalizedSlug = normalizeSlugForCache(slug);
1609
+ const existing = readCachedCloudContent(keyFingerprint);
1610
+ writeCachedCloudContent({
1611
+ keyFingerprint,
1612
+ savedAt: Date.now(),
1613
+ siteConfig: state.site ?? null,
1614
+ pages: {
1615
+ ...(existing?.pages ?? {}),
1616
+ [normalizedSlug]: state.page,
1617
+ },
1618
+ });
1600
1619
  },
1601
1620
  showLegacySave: !isCloudMode,
1602
1621
  showHotSave: isCloudMode,
@@ -1667,6 +1686,26 @@ function App() {
1667
1686
  </div>
1668
1687
  </>
1669
1688
  ) : null}
1689
+ {isCloudMode && !hasInitialCloudResolved ? (
1690
+ <div className="fixed inset-0 z-[1290] bg-background/80 backdrop-blur-sm">
1691
+ <div className="mx-auto w-full max-w-[1600px] p-6">
1692
+ <div className="grid gap-4 lg:grid-cols-[1fr_420px]">
1693
+ <div className="space-y-4">
1694
+ <Skeleton className="h-10 w-64" />
1695
+ <Skeleton className="h-[220px] w-full rounded-xl" />
1696
+ <Skeleton className="h-[220px] w-full rounded-xl" />
1697
+ </div>
1698
+ <div className="space-y-3 rounded-xl border border-border/50 bg-card/60 p-4">
1699
+ <Skeleton className="h-8 w-32" />
1700
+ <Skeleton className="h-5 w-full" />
1701
+ <Skeleton className="h-5 w-5/6" />
1702
+ <Skeleton className="h-5 w-4/6" />
1703
+ <Skeleton className="h-24 w-full rounded-lg" />
1704
+ </div>
1705
+ </div>
1706
+ </div>
1707
+ </div>
1708
+ ) : null}
1670
1709
  {shouldRenderEngine ? <JsonPagesEngine config={config} /> : null}
1671
1710
  {isCloudMode && (contentMode === 'error' || contentFallback?.reasonCode === 'CLOUD_REFRESH_FAILED') ? (
1672
1711
  <div
@@ -7186,6 +7225,26 @@ export { Separator }
7186
7225
 
7187
7226
 
7188
7227
 
7228
+ END_OF_FILE_CONTENT
7229
+ echo "Creating src/components/ui/skeleton.tsx..."
7230
+ cat << 'END_OF_FILE_CONTENT' > "src/components/ui/skeleton.tsx"
7231
+ import { cn } from '@/lib/utils';
7232
+ import type { HTMLAttributes } from 'react';
7233
+
7234
+ function Skeleton({
7235
+ className,
7236
+ ...props
7237
+ }: HTMLAttributes<HTMLDivElement>) {
7238
+ return (
7239
+ <div
7240
+ className={cn('animate-pulse rounded-md bg-muted', className)}
7241
+ {...props}
7242
+ />
7243
+ );
7244
+ }
7245
+
7246
+ export { Skeleton };
7247
+
7189
7248
  END_OF_FILE_CONTENT
7190
7249
  echo "Creating src/components/ui/textarea.tsx..."
7191
7250
  cat << 'END_OF_FILE_CONTENT' > "src/components/ui/textarea.tsx"
@@ -596,7 +596,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
596
596
  "@tiptap/extension-link": "^2.11.5",
597
597
  "@tiptap/react": "^2.11.5",
598
598
  "@tiptap/starter-kit": "^2.11.5",
599
- "@olonjs/core": "^1.0.70",
599
+ "@olonjs/core": "^1.0.71",
600
600
  "clsx": "^2.1.1",
601
601
  "lucide-react": "^0.474.0",
602
602
  "react": "^19.0.0",
@@ -1074,6 +1074,30 @@ function App() {
1074
1074
  const body = (await res.json().catch(() => ({}))) as { error?: string };
1075
1075
  if (!res.ok) throw new Error(body.error ?? `Save to file failed: ${res.status}`);
1076
1076
  },
1077
+ async hotSave(state: ProjectState, slug: string): Promise<void> {
1078
+ if (!isCloudMode || !CLOUD_API_URL || !CLOUD_API_KEY) {
1079
+ throw new Error('Cloud mode is not configured for hot save.');
1080
+ }
1081
+ const apiBase = CLOUD_API_URL.replace(/\/$/, '');
1082
+ const res = await fetch(`${apiBase}/hotSave`, {
1083
+ method: 'POST',
1084
+ headers: {
1085
+ 'Content-Type': 'application/json',
1086
+ Authorization: `Bearer ${CLOUD_API_KEY}`,
1087
+ },
1088
+ body: JSON.stringify({
1089
+ slug,
1090
+ page: state.page,
1091
+ siteConfig: state.site,
1092
+ }),
1093
+ });
1094
+ const body = (await res.json().catch(() => ({}))) as { error?: string; code?: string };
1095
+ if (!res.ok) {
1096
+ throw new Error(body.error || body.code || `Hot save failed: ${res.status}`);
1097
+ }
1098
+ },
1099
+ showLegacySave: !isCloudMode,
1100
+ showHotSave: isCloudMode,
1077
1101
  },
1078
1102
  assets: {
1079
1103
  assetsBaseUrl: '/assets',
@@ -596,7 +596,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
596
596
  "@tiptap/extension-link": "^2.11.5",
597
597
  "@tiptap/react": "^2.11.5",
598
598
  "@tiptap/starter-kit": "^2.11.5",
599
- "@olonjs/core": "^1.0.70",
599
+ "@olonjs/core": "^1.0.71",
600
600
  "clsx": "^2.1.1",
601
601
  "lucide-react": "^0.474.0",
602
602
  "react": "^19.0.0",
@@ -941,6 +941,7 @@ import themeData from '@/data/config/theme.json';
941
941
  import menuData from '@/data/config/menu.json';
942
942
  import { getFilePages } from '@/lib/getFilePages';
943
943
  import { DopaDrawer } from '@/components/save-drawer/DopaDrawer';
944
+ import { Skeleton } from '@/components/ui/skeleton';
944
945
 
945
946
  import tenantCss from './index.css?inline';
946
947
 
@@ -1194,6 +1195,16 @@ function cloudFingerprint(apiBase: string, apiKey: string): string {
1194
1195
  return `${normalizeApiBase(apiBase)}::${apiKey.slice(-8)}`;
1195
1196
  }
1196
1197
 
1198
+ function normalizeSlugForCache(slug: string): string {
1199
+ return (
1200
+ slug
1201
+ .trim()
1202
+ .toLowerCase()
1203
+ .replace(/[^a-z0-9/_-]/g, '-')
1204
+ .replace(/^\/+|\/+$/g, '') || 'home'
1205
+ );
1206
+ }
1207
+
1197
1208
  function readCachedCloudContent(fingerprint: string): CachedCloudContent | null {
1198
1209
  try {
1199
1210
  const raw = localStorage.getItem(CLOUD_CACHE_KEY);
@@ -1248,7 +1259,6 @@ function App() {
1248
1259
  const activeCloudSaveController = useRef<AbortController | null>(null);
1249
1260
  const contentLoadInFlight = useRef<Promise<void> | null>(null);
1250
1261
  const pendingCloudSave = useRef<{ state: ProjectState; slug: string } | null>(null);
1251
- const warmBootFromCloudCache = useRef(false);
1252
1262
  const cloudApiCandidates = useMemo(
1253
1263
  () => (isCloudMode && CLOUD_API_URL ? buildApiCandidates(CLOUD_API_URL) : []),
1254
1264
  [isCloudMode, CLOUD_API_URL]
@@ -1288,26 +1298,16 @@ function App() {
1288
1298
  const primaryApiBase = cloudApiCandidates[0] ?? normalizeApiBase(CLOUD_API_URL);
1289
1299
  const fingerprint = cloudFingerprint(primaryApiBase, CLOUD_API_KEY);
1290
1300
  const cached = readCachedCloudContent(fingerprint);
1301
+ const cachedPages = cached ? toPagesRecord(cached.pages) : null;
1302
+ const cachedSite = cached ? coerceSiteConfig(cached.siteConfig) : null;
1303
+ const hasCachedFallback = Boolean((cachedPages && Object.keys(cachedPages).length > 0) || cachedSite);
1291
1304
  if (cached) {
1292
- const cachedPages = toPagesRecord(cached.pages);
1293
- const cachedSite = coerceSiteConfig(cached.siteConfig);
1294
- if (cachedPages && Object.keys(cachedPages).length > 0) {
1295
- setPages(cachedPages);
1296
- }
1297
- if (cachedSite) {
1298
- setSiteConfig(cachedSite);
1299
- }
1300
- setContentMode('cloud');
1301
- setContentFallback(null);
1302
- warmBootFromCloudCache.current = true;
1303
- setShowTopProgress(false);
1304
- setHasInitialCloudResolved(true);
1305
1305
  logBootstrapEvent('boot.cloud.cache_hit', { ageMs: Date.now() - cached.savedAt });
1306
- } else {
1307
- warmBootFromCloudCache.current = false;
1308
- setShowTopProgress(true);
1309
- setHasInitialCloudResolved(false);
1310
1306
  }
1307
+ setContentMode('cloud');
1308
+ setContentFallback(null);
1309
+ setShowTopProgress(true);
1310
+ setHasInitialCloudResolved(false);
1311
1311
  logBootstrapEvent('boot.start', { mode: 'cloud', apiCandidates: cloudApiCandidates.length });
1312
1312
 
1313
1313
  const loadCloudContent = async () => {
@@ -1320,6 +1320,7 @@ function App() {
1320
1320
  try {
1321
1321
  const res = await fetch(`${apiBase}/content`, {
1322
1322
  method: 'GET',
1323
+ cache: 'no-store',
1323
1324
  headers: {
1324
1325
  Authorization: `Bearer ${CLOUD_API_KEY}`,
1325
1326
  },
@@ -1422,7 +1423,13 @@ function App() {
1422
1423
  } catch (error: unknown) {
1423
1424
  if (controller.signal.aborted) return;
1424
1425
  const failure = toCloudLoadFailure(error);
1425
- if (warmBootFromCloudCache.current) {
1426
+ if (hasCachedFallback) {
1427
+ if (cachedPages && Object.keys(cachedPages).length > 0) {
1428
+ setPages(cachedPages);
1429
+ }
1430
+ if (cachedSite) {
1431
+ setSiteConfig(cachedSite);
1432
+ }
1426
1433
  setContentMode('cloud');
1427
1434
  setContentFallback({
1428
1435
  reasonCode: 'CLOUD_REFRESH_FAILED',
@@ -1589,14 +1596,26 @@ function App() {
1589
1596
  },
1590
1597
  body: JSON.stringify({
1591
1598
  slug,
1592
- type: 'page',
1593
- data: state.page,
1599
+ page: state.page,
1600
+ siteConfig: state.site,
1594
1601
  }),
1595
1602
  });
1596
1603
  const body = (await res.json().catch(() => ({}))) as { error?: string; code?: string };
1597
1604
  if (!res.ok) {
1598
1605
  throw new Error(body.error || body.code || `Hot save failed: ${res.status}`);
1599
1606
  }
1607
+ const keyFingerprint = cloudFingerprint(apiBase, CLOUD_API_KEY);
1608
+ const normalizedSlug = normalizeSlugForCache(slug);
1609
+ const existing = readCachedCloudContent(keyFingerprint);
1610
+ writeCachedCloudContent({
1611
+ keyFingerprint,
1612
+ savedAt: Date.now(),
1613
+ siteConfig: state.site ?? null,
1614
+ pages: {
1615
+ ...(existing?.pages ?? {}),
1616
+ [normalizedSlug]: state.page,
1617
+ },
1618
+ });
1600
1619
  },
1601
1620
  showLegacySave: !isCloudMode,
1602
1621
  showHotSave: isCloudMode,
@@ -1667,6 +1686,26 @@ function App() {
1667
1686
  </div>
1668
1687
  </>
1669
1688
  ) : null}
1689
+ {isCloudMode && !hasInitialCloudResolved ? (
1690
+ <div className="fixed inset-0 z-[1290] bg-background/80 backdrop-blur-sm">
1691
+ <div className="mx-auto w-full max-w-[1600px] p-6">
1692
+ <div className="grid gap-4 lg:grid-cols-[1fr_420px]">
1693
+ <div className="space-y-4">
1694
+ <Skeleton className="h-10 w-64" />
1695
+ <Skeleton className="h-[220px] w-full rounded-xl" />
1696
+ <Skeleton className="h-[220px] w-full rounded-xl" />
1697
+ </div>
1698
+ <div className="space-y-3 rounded-xl border border-border/50 bg-card/60 p-4">
1699
+ <Skeleton className="h-8 w-32" />
1700
+ <Skeleton className="h-5 w-full" />
1701
+ <Skeleton className="h-5 w-5/6" />
1702
+ <Skeleton className="h-5 w-4/6" />
1703
+ <Skeleton className="h-24 w-full rounded-lg" />
1704
+ </div>
1705
+ </div>
1706
+ </div>
1707
+ </div>
1708
+ ) : null}
1670
1709
  {shouldRenderEngine ? <JsonPagesEngine config={config} /> : null}
1671
1710
  {isCloudMode && (contentMode === 'error' || contentFallback?.reasonCode === 'CLOUD_REFRESH_FAILED') ? (
1672
1711
  <div
@@ -7186,6 +7225,26 @@ export { Separator }
7186
7225
 
7187
7226
 
7188
7227
 
7228
+ END_OF_FILE_CONTENT
7229
+ echo "Creating src/components/ui/skeleton.tsx..."
7230
+ cat << 'END_OF_FILE_CONTENT' > "src/components/ui/skeleton.tsx"
7231
+ import { cn } from '@/lib/utils';
7232
+ import type { HTMLAttributes } from 'react';
7233
+
7234
+ function Skeleton({
7235
+ className,
7236
+ ...props
7237
+ }: HTMLAttributes<HTMLDivElement>) {
7238
+ return (
7239
+ <div
7240
+ className={cn('animate-pulse rounded-md bg-muted', className)}
7241
+ {...props}
7242
+ />
7243
+ );
7244
+ }
7245
+
7246
+ export { Skeleton };
7247
+
7189
7248
  END_OF_FILE_CONTENT
7190
7249
  echo "Creating src/components/ui/textarea.tsx..."
7191
7250
  cat << 'END_OF_FILE_CONTENT' > "src/components/ui/textarea.tsx"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olonjs/cli",
3
- "version": "3.0.82",
3
+ "version": "3.0.83",
4
4
  "description": "The Sovereign CLI Engine for OlonJS.",
5
5
  "type": "module",
6
6
  "bin": {