@wix/himalaya-cli 0.1156.0 → 0.1158.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts +8 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/validator.d.ts +14 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +8 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +4 -1
- package/dist/cli.mjs +85 -27
- package/guides/himi-skill.md +8 -0
- package/guides/index.json +6 -0
- package/guides/widgets.md +233 -0
- package/package.json +2 -2
|
@@ -137,6 +137,8 @@ export interface WebViewConfig {
|
|
|
137
137
|
coverBandColor?: string;
|
|
138
138
|
hideWixAds?: boolean;
|
|
139
139
|
hideHostedSiteFooter?: boolean;
|
|
140
|
+
sessionBridge?: boolean;
|
|
141
|
+
hostedSiteAdapter?: "" | "wix" | "base44";
|
|
140
142
|
}
|
|
141
143
|
export declare function vstack(props: {
|
|
142
144
|
spacing?: number;
|
package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts
CHANGED
|
@@ -3036,6 +3036,14 @@ export interface WebViewConfig {
|
|
|
3036
3036
|
* script, like {@link hideHostedSiteHeader}. No-op on web (cross-origin iframe).
|
|
3037
3037
|
*/
|
|
3038
3038
|
hideHostedSiteFooter?: boolean;
|
|
3039
|
+
/**
|
|
3040
|
+
* RFC 0062 §5.8 — sign the page in natively. Runs only when {@link hostedSiteAdapter} names an
|
|
3041
|
+
* adapter AND the app declares that adapter's capability; the host seeds the adapter's session
|
|
3042
|
+
* into the page's own storage at document start, on the exact {@link allowedOrigins} only.
|
|
3043
|
+
*/
|
|
3044
|
+
sessionBridge?: boolean;
|
|
3045
|
+
/** RFC 0062 §5.8 — which site adapter the host runs: `""` (none) | `"wix"` | `"base44"`. */
|
|
3046
|
+
hostedSiteAdapter?: "" | "wix" | "base44";
|
|
3039
3047
|
}
|
|
3040
3048
|
/**
|
|
3041
3049
|
* Embeddable WebView component (web-in-SDUI). WebViews have no intrinsic height,
|
package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/validator.d.ts
CHANGED
|
@@ -1051,6 +1051,13 @@ export declare const screenDescriptorSchema: {
|
|
|
1051
1051
|
readonly hideHostedSiteFooter: {
|
|
1052
1052
|
readonly type: "boolean";
|
|
1053
1053
|
};
|
|
1054
|
+
readonly sessionBridge: {
|
|
1055
|
+
readonly type: "boolean";
|
|
1056
|
+
};
|
|
1057
|
+
readonly hostedSiteAdapter: {
|
|
1058
|
+
readonly type: "string";
|
|
1059
|
+
readonly enum: readonly ["", "wix", "base44"];
|
|
1060
|
+
};
|
|
1054
1061
|
};
|
|
1055
1062
|
};
|
|
1056
1063
|
};
|
|
@@ -2012,6 +2019,13 @@ export declare const widgetSnapshotSchema: {
|
|
|
2012
2019
|
readonly hideHostedSiteFooter: {
|
|
2013
2020
|
readonly type: "boolean";
|
|
2014
2021
|
};
|
|
2022
|
+
readonly sessionBridge: {
|
|
2023
|
+
readonly type: "boolean";
|
|
2024
|
+
};
|
|
2025
|
+
readonly hostedSiteAdapter: {
|
|
2026
|
+
readonly type: "string";
|
|
2027
|
+
readonly enum: readonly ["", "wix", "base44"];
|
|
2028
|
+
};
|
|
2015
2029
|
};
|
|
2016
2030
|
};
|
|
2017
2031
|
};
|
|
@@ -8634,7 +8634,10 @@ var screenDescriptorSchema = {
|
|
|
8634
8634
|
coverBandColor: { type: "string" },
|
|
8635
8635
|
// RFC 0062 — binary-shipped chrome hiding for a hosted site (no page script over the air).
|
|
8636
8636
|
hideWixAds: { type: "boolean" },
|
|
8637
|
-
hideHostedSiteFooter: { type: "boolean" }
|
|
8637
|
+
hideHostedSiteFooter: { type: "boolean" },
|
|
8638
|
+
// RFC 0062 §5.8 — the native session bridge (fields 47/48).
|
|
8639
|
+
sessionBridge: { type: "boolean" },
|
|
8640
|
+
hostedSiteAdapter: { type: "string", enum: ["", "wix", "base44"] }
|
|
8638
8641
|
}
|
|
8639
8642
|
}
|
|
8640
8643
|
}
|
|
@@ -13169,6 +13172,10 @@ function siteWebViewPreset(site) {
|
|
|
13169
13172
|
hideHostedSiteFooter: chrome.hideSiteFooter,
|
|
13170
13173
|
// Wix ads only exist on Wix free sites; a Base44 site never gets the Wix selectors.
|
|
13171
13174
|
hideWixAds: site.kind === "wix" && chrome.hideWixAds,
|
|
13175
|
+
// RFC 0062 §5.8 — a Base44 app is signed in natively and its session carried into the page.
|
|
13176
|
+
// The host still refuses unless the app declares the `base44` capability. Wix keeps its
|
|
13177
|
+
// item-2 bridge (not built yet), so a Wix site carries neither field.
|
|
13178
|
+
...site.kind === "base44" ? { hostedSiteAdapter: "base44", sessionBridge: true } : {},
|
|
13172
13179
|
pullToRefresh: true,
|
|
13173
13180
|
showProgressBar: true,
|
|
13174
13181
|
syncStatusBarAppearance: true,
|
|
@@ -7587,7 +7587,10 @@ var screenDescriptorSchema = {
|
|
|
7587
7587
|
coverBandColor: { type: "string" },
|
|
7588
7588
|
// RFC 0062 — binary-shipped chrome hiding for a hosted site (no page script over the air).
|
|
7589
7589
|
hideWixAds: { type: "boolean" },
|
|
7590
|
-
hideHostedSiteFooter: { type: "boolean" }
|
|
7590
|
+
hideHostedSiteFooter: { type: "boolean" },
|
|
7591
|
+
// RFC 0062 §5.8 — the native session bridge (fields 47/48).
|
|
7592
|
+
sessionBridge: { type: "boolean" },
|
|
7593
|
+
hostedSiteAdapter: { type: "string", enum: ["", "wix", "base44"] }
|
|
7591
7594
|
}
|
|
7592
7595
|
}
|
|
7593
7596
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -6834,6 +6834,7 @@ function resolveTranslation(value, catalog, sourceCatalog) {
|
|
|
6834
6834
|
var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
|
|
6835
6835
|
var init_bindings = __esm({
|
|
6836
6836
|
"../../core/ts/src/bindings.ts"() {
|
|
6837
|
+
"use strict";
|
|
6837
6838
|
BINDING_SIGILS = ["service", "flag", "item", "t"];
|
|
6838
6839
|
BINDING_SIGIL_RE = new RegExp(
|
|
6839
6840
|
`^\\$(?:${BINDING_SIGILS.filter((s) => s !== "t").join("|")})\\b|^\\$t:`
|
|
@@ -7672,7 +7673,10 @@ var init_validator = __esm({
|
|
|
7672
7673
|
coverBandColor: { type: "string" },
|
|
7673
7674
|
// RFC 0062 — binary-shipped chrome hiding for a hosted site (no page script over the air).
|
|
7674
7675
|
hideWixAds: { type: "boolean" },
|
|
7675
|
-
hideHostedSiteFooter: { type: "boolean" }
|
|
7676
|
+
hideHostedSiteFooter: { type: "boolean" },
|
|
7677
|
+
// RFC 0062 §5.8 — the native session bridge (fields 47/48).
|
|
7678
|
+
sessionBridge: { type: "boolean" },
|
|
7679
|
+
hostedSiteAdapter: { type: "string", enum: ["", "wix", "base44"] }
|
|
7676
7680
|
}
|
|
7677
7681
|
}
|
|
7678
7682
|
}
|
|
@@ -73422,6 +73426,7 @@ function pageId(c, used) {
|
|
|
73422
73426
|
}
|
|
73423
73427
|
function deriveSiteShell(input) {
|
|
73424
73428
|
const origin = normalizeSiteOrigin(input.siteUrl);
|
|
73429
|
+
const kind = input.kind ?? siteKindForOrigin(origin);
|
|
73425
73430
|
const notes = [];
|
|
73426
73431
|
const published = input.look.sitePages ?? [];
|
|
73427
73432
|
const mainPageId = input.look.mainPageId;
|
|
@@ -73440,7 +73445,14 @@ function deriveSiteShell(input) {
|
|
|
73440
73445
|
};
|
|
73441
73446
|
const navLinks = input.look.navLinks ?? [];
|
|
73442
73447
|
let homeTitle = "Home";
|
|
73443
|
-
|
|
73448
|
+
const routes = (input.routes ?? []).filter((r) => isNavigableRoute(r.path));
|
|
73449
|
+
if (routes.length) {
|
|
73450
|
+
for (const r of routes) {
|
|
73451
|
+
const path2 = r.path.startsWith("/") ? r.path : `/${r.path}`;
|
|
73452
|
+
add(routeTitle(r), path2, `${origin.replace(/\/$/, "")}${path2}`);
|
|
73453
|
+
}
|
|
73454
|
+
notes.push(`site pages: taken from the Base44 app book's ${routes.length} route(s) \u2014 edit \`site.pages\` to rename or reorder them.`);
|
|
73455
|
+
} else if (navLinks.length) {
|
|
73444
73456
|
for (const link of navLinks) {
|
|
73445
73457
|
const path2 = pathOnSite(origin, link.href);
|
|
73446
73458
|
if (path2 === null) continue;
|
|
@@ -73501,22 +73513,39 @@ function deriveSiteShell(input) {
|
|
|
73501
73513
|
...placement === "tab" ? {} : { placement }
|
|
73502
73514
|
}))
|
|
73503
73515
|
];
|
|
73504
|
-
if (!published.length && !navLinks.length) {
|
|
73516
|
+
if (!published.length && !navLinks.length && !routes.length) {
|
|
73505
73517
|
notes.push("site pages: the analysis read no page list, so the map has only Home \u2014 add the site's pages to `site.pages` before --apply.");
|
|
73506
73518
|
}
|
|
73507
73519
|
const site = {
|
|
73508
73520
|
origin,
|
|
73509
|
-
kind
|
|
73521
|
+
kind,
|
|
73510
73522
|
pages,
|
|
73511
73523
|
// Hidden by default — the product decision for the site shell (RFC 0062 §6); the owner can
|
|
73512
|
-
// turn any of them back on here, and the flags ride the OTA descriptor.
|
|
73513
|
-
|
|
73524
|
+
// turn any of them back on here, and the flags ride the OTA descriptor. A Base44 app has no
|
|
73525
|
+
// Wix ads to hide, so it never carries the flag (RFC 0062 §5.8).
|
|
73526
|
+
chrome: kind === "wix" ? { hideSiteHeader: true, hideSiteFooter: true, hideWixAds: true } : { hideSiteHeader: true, hideSiteFooter: true }
|
|
73514
73527
|
};
|
|
73515
73528
|
assertSiteShellConfig(site);
|
|
73516
73529
|
const tabTitles = pages.filter((p) => (p.placement ?? "tab") === "tab").map((p) => p.title);
|
|
73517
73530
|
notes.unshift(`site pages: ${pages.length} derived \u2014 tabs ${tabTitles.join(", ")}; edit \`site.pages\` in provisioning.overlay.json to change them.`);
|
|
73518
73531
|
return { site, notes };
|
|
73519
73532
|
}
|
|
73533
|
+
function siteKindForOrigin(origin) {
|
|
73534
|
+
const host = new URL(origin).hostname.toLowerCase();
|
|
73535
|
+
const isBase44 = ["base44.app", "base44.com"].some((d) => host === d || host.endsWith(`.${d}`));
|
|
73536
|
+
return isBase44 ? "base44" : "wix";
|
|
73537
|
+
}
|
|
73538
|
+
function isNavigableRoute(path2) {
|
|
73539
|
+
const p = path2.trim();
|
|
73540
|
+
if (!p || p === "/" || p.includes(":") || p.includes("*")) return false;
|
|
73541
|
+
return !/^\/?(login|signin|sign-in|register|signup|logout)(\/|$)/i.test(p);
|
|
73542
|
+
}
|
|
73543
|
+
function routeTitle(r) {
|
|
73544
|
+
const fromComponent = r.component?.replace(/(Page|Screen|View)$/, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim();
|
|
73545
|
+
if (fromComponent) return fromComponent;
|
|
73546
|
+
const seg = r.path.split("/").filter(Boolean).pop() ?? r.path;
|
|
73547
|
+
return seg.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
73548
|
+
}
|
|
73520
73549
|
var DERIVED_SITE_MAX_PAGES, SITE_PAGE_TITLE_MAX, ROLE_ICON, TAB_ROLES, SYSTEM_SEGMENT, SYSTEM_TITLE, HOME_WORD, oneLine2;
|
|
73521
73550
|
var init_site_shell_derive = __esm({
|
|
73522
73551
|
"src/site-analyze/site-shell-derive.ts"() {
|
|
@@ -74174,12 +74203,13 @@ __export(cli_exports2, {
|
|
|
74174
74203
|
SITE_FUNCTION_USAGE: () => SITE_FUNCTION_USAGE,
|
|
74175
74204
|
applyNeedsClient: () => applyNeedsClient,
|
|
74176
74205
|
assetFetchFor: () => assetFetchFor,
|
|
74206
|
+
base44BookRoutes: () => base44BookRoutes,
|
|
74177
74207
|
noteWithWarnings: () => noteWithWarnings,
|
|
74178
74208
|
ownerSiteFor: () => ownerSiteFor,
|
|
74179
74209
|
runSiteAnalyze: () => runSiteAnalyze,
|
|
74180
74210
|
runSiteFunctionCli: () => runSiteFunctionCli
|
|
74181
74211
|
});
|
|
74182
|
-
import { readFileSync as readFileSync88 } from "node:fs";
|
|
74212
|
+
import { existsSync as existsSync71, readFileSync as readFileSync88 } from "node:fs";
|
|
74183
74213
|
import { resolve as resolve55 } from "node:path";
|
|
74184
74214
|
function applyNeedsClient(shell) {
|
|
74185
74215
|
if (shell === void 0 || !SHELL_NAMES2.includes(shell)) return true;
|
|
@@ -74230,7 +74260,11 @@ async function runSiteAnalyze(opts) {
|
|
|
74230
74260
|
);
|
|
74231
74261
|
if (opts.shell !== void 0 && isSiteShell(opts.shell)) {
|
|
74232
74262
|
try {
|
|
74233
|
-
const
|
|
74263
|
+
const kind = siteKindForOrigin(result.book.input.siteUrl);
|
|
74264
|
+
const book = kind === "base44" ? base44BookRoutes(resolve55(cwd, ".himi/base44-book/book.json"), result.book.input.siteUrl) : void 0;
|
|
74265
|
+
const routes = book?.routes;
|
|
74266
|
+
if (book?.skipped) result.warnings = [...result.warnings ?? [], book.skipped];
|
|
74267
|
+
const derived = deriveSiteShell({ siteUrl: result.book.input.siteUrl, look: result.book.look, kind, routes });
|
|
74234
74268
|
result.provisioningOverlay.site = derived.site;
|
|
74235
74269
|
const warnings2 = (result.warnings ?? []).map(
|
|
74236
74270
|
(w) => !opts.clientId?.trim() && w.startsWith("no --client-id:") ? `no --client-id: fine for --shell ${opts.shell} \u2014 a site shell binds no client, so --apply works without one. Only the vertical probes were skipped, so the catalog has no counts.` : w
|
|
@@ -74325,6 +74359,30 @@ async function runSiteFunctionCli(opts) {
|
|
|
74325
74359
|
fetchImpl: opts.fetchImpl
|
|
74326
74360
|
});
|
|
74327
74361
|
}
|
|
74362
|
+
function base44BookRoutes(bookPath, siteUrl) {
|
|
74363
|
+
if (!existsSync71(bookPath)) return void 0;
|
|
74364
|
+
try {
|
|
74365
|
+
const book = JSON.parse(readFileSync88(bookPath, "utf8"));
|
|
74366
|
+
if (book.kind !== "base44-app-book" || !Array.isArray(book.routes)) return void 0;
|
|
74367
|
+
const host = (u) => {
|
|
74368
|
+
try {
|
|
74369
|
+
return typeof u === "string" ? new URL(u).host.toLowerCase() : "";
|
|
74370
|
+
} catch {
|
|
74371
|
+
return "";
|
|
74372
|
+
}
|
|
74373
|
+
};
|
|
74374
|
+
const want = host(siteUrl);
|
|
74375
|
+
const have = [host(book.input?.url), host(book.identity?.publishedUrl)].filter(Boolean);
|
|
74376
|
+
if (!have.includes(want)) {
|
|
74377
|
+
return { skipped: `site pages: ignored .himi/base44-book \u2014 it was read from ${have[0] || "an unknown app"}, not ${want}; re-run \`himi base44 analyze --url ${siteUrl}\` for this site.` };
|
|
74378
|
+
}
|
|
74379
|
+
return {
|
|
74380
|
+
routes: book.routes.filter((r) => !!r && typeof r.path === "string").map((r) => ({ path: r.path, ...typeof r.component === "string" ? { component: r.component } : {} }))
|
|
74381
|
+
};
|
|
74382
|
+
} catch {
|
|
74383
|
+
return void 0;
|
|
74384
|
+
}
|
|
74385
|
+
}
|
|
74328
74386
|
var APPLY_NEEDS_CLIENT, FUNCTION_OPS;
|
|
74329
74387
|
var init_cli2 = __esm({
|
|
74330
74388
|
"src/site-analyze/cli.ts"() {
|
|
@@ -74559,7 +74617,7 @@ __export(site_create_exports, {
|
|
|
74559
74617
|
runSiteList: () => runSiteList,
|
|
74560
74618
|
writeProvisioningBinding: () => writeProvisioningBinding
|
|
74561
74619
|
});
|
|
74562
|
-
import { existsSync as
|
|
74620
|
+
import { existsSync as existsSync72, readFileSync as readFileSync89, writeFileSync as writeFileSync34 } from "node:fs";
|
|
74563
74621
|
import { resolve as resolvePath2 } from "node:path";
|
|
74564
74622
|
function oauthClientName(projectName) {
|
|
74565
74623
|
return `${projectName} client`;
|
|
@@ -74708,7 +74766,7 @@ async function runSiteCreate(opts) {
|
|
|
74708
74766
|
if (!wantOauthApp) return { ...base, nextSteps: nextSteps(metaSiteId) };
|
|
74709
74767
|
return { ...base, clientId, redirectUri, nextSteps: nextSteps(metaSiteId, clientId) };
|
|
74710
74768
|
}
|
|
74711
|
-
function writeProvisioningBinding(target, binding2, fs = { existsSync:
|
|
74769
|
+
function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync72, readFileSync: readFileSync89, writeFileSync: writeFileSync34 }) {
|
|
74712
74770
|
const msid = binding2.siteId;
|
|
74713
74771
|
const path2 = resolvePath2(target);
|
|
74714
74772
|
const committed = committedShellProvisioning(path2);
|
|
@@ -75381,7 +75439,7 @@ var init_context = __esm({
|
|
|
75381
75439
|
});
|
|
75382
75440
|
|
|
75383
75441
|
// src/cli.ts
|
|
75384
|
-
import { mkdtempSync as mkdtempSync5, readFileSync as readFileSync90, mkdirSync as mkdirSync39, writeFileSync as writeFileSync35, existsSync as
|
|
75442
|
+
import { mkdtempSync as mkdtempSync5, readFileSync as readFileSync90, mkdirSync as mkdirSync39, writeFileSync as writeFileSync35, existsSync as existsSync73, rmSync as rmSync20, readdirSync as readdirSync32, statSync as statSync20, lstatSync as lstatSync17, accessSync, constants as constants3, renameSync as renameSync9, cpSync as cpSync3 } from "node:fs";
|
|
75385
75443
|
import { execFile as execFile2, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn7 } from "node:child_process";
|
|
75386
75444
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
75387
75445
|
import { createHash as createHash36, randomUUID as randomUUID8 } from "node:crypto";
|
|
@@ -78609,7 +78667,7 @@ function createUpdateRunner(spawn8 = spawnSync5) {
|
|
|
78609
78667
|
reexec: (argv) => {
|
|
78610
78668
|
const root = globalNodeModulesRoot();
|
|
78611
78669
|
const entry = root ? join74(root, "@wix", "himalaya-cli", "dist", "cli.mjs") : void 0;
|
|
78612
|
-
if (!entry || !
|
|
78670
|
+
if (!entry || !existsSync73(entry)) return void 0;
|
|
78613
78671
|
const result = spawnSync5(process.execPath, [entry, ...argv], {
|
|
78614
78672
|
// inherit keeps PIPED STDIN working — commands that read stdin must survive a re-exec.
|
|
78615
78673
|
stdio: "inherit",
|
|
@@ -79380,7 +79438,7 @@ async function nativeConfigAdvisories(app, configSource) {
|
|
|
79380
79438
|
const { iosProjectYml: iosProjectYml2 } = await Promise.resolve().then(() => (init_paths2(), paths_exports));
|
|
79381
79439
|
const { androidOptedIn: androidOptedIn3, LABEL_IOS_SPLASH: LABEL_IOS_SPLASH2 } = await Promise.resolve().then(() => (init_splash(), splash_exports));
|
|
79382
79440
|
const iosYmlPath = iosProjectYml2(app);
|
|
79383
|
-
const iosYml =
|
|
79441
|
+
const iosYml = existsSync73(iosYmlPath) ? readFileSync90(iosYmlPath, "utf8") : "";
|
|
79384
79442
|
let optedIn = iosYml.includes(LABEL_IOS_SPLASH2);
|
|
79385
79443
|
if (!optedIn) {
|
|
79386
79444
|
const { androidManifestPath: androidManifestPath3 } = await Promise.resolve().then(() => (init_gen_native_config(), gen_native_config_exports));
|
|
@@ -79446,7 +79504,7 @@ function keptPlan(kept) {
|
|
|
79446
79504
|
}
|
|
79447
79505
|
async function initFromRemoteTemplate(name, parentDir, template, c) {
|
|
79448
79506
|
const dir = resolve56(parentDir, name);
|
|
79449
|
-
if (
|
|
79507
|
+
if (existsSync73(join74(dir, "himalaya.content.json"))) {
|
|
79450
79508
|
throw new Error(`content package already exists at ${dir}`);
|
|
79451
79509
|
}
|
|
79452
79510
|
const r = await c.fetchTemplateSource(TEMPLATE_APP_PREFIX + template);
|
|
@@ -79489,7 +79547,7 @@ function symlinkDestinationIssue(dest) {
|
|
|
79489
79547
|
function restoreDestinationIssue(dest, force) {
|
|
79490
79548
|
const link = symlinkDestinationIssue(dest);
|
|
79491
79549
|
if (link) return link;
|
|
79492
|
-
if (!
|
|
79550
|
+
if (!existsSync73(dest)) return null;
|
|
79493
79551
|
if (!statSync20(dest).isDirectory()) {
|
|
79494
79552
|
return { error: `${dest} exists and is not a directory`, hint: "pass --out/--name to land somewhere else, or remove the file" };
|
|
79495
79553
|
}
|
|
@@ -79504,7 +79562,7 @@ function restoreDestinationIssue(dest, force) {
|
|
|
79504
79562
|
function isHimalayaMonorepoCheckout(start) {
|
|
79505
79563
|
let dir = resolve56(start);
|
|
79506
79564
|
for (; ; ) {
|
|
79507
|
-
if (
|
|
79565
|
+
if (existsSync73(join74(dir, "tools", "himi-cli", "src", "cli.ts")) && existsSync73(join74(dir, "core", "serve", "src", "server.ts"))) return true;
|
|
79508
79566
|
const parent = dirname40(dir);
|
|
79509
79567
|
if (parent === dir) return false;
|
|
79510
79568
|
dir = parent;
|
|
@@ -79832,7 +79890,7 @@ Seed your coding agent with \`himi agent-init\`. Guides: ${idx.portalUrl}`);
|
|
|
79832
79890
|
...res.removed.length ? { removed: res.removed, note: "older copies at the app root, moved to .himi/legacy-agent-files/ (a backup, in case one became app code) \u2014 the current ones live in .agents/skills/himalaya/" } : {},
|
|
79833
79891
|
// In a git repo the refresh moves the diff identity an earlier `himi test` attested (that
|
|
79834
79892
|
// hash is shared with the repo gates and deliberately exempts nothing but its own metadata).
|
|
79835
|
-
...
|
|
79893
|
+
...existsSync73(join74(dir, ".himi", "attestations", "latest.json")) ? { next: "if `himi push` then refuses the attestation as stale, re-run `himi test` \u2014 in a git checkout this refresh changed the tree it covered" } : {},
|
|
79836
79894
|
...res.skipped.length ? { skipped: res.skipped } : {},
|
|
79837
79895
|
portalUrl: idx.portalUrl
|
|
79838
79896
|
});
|
|
@@ -80065,7 +80123,7 @@ async function producedWidgetFamilies(dir) {
|
|
|
80065
80123
|
}
|
|
80066
80124
|
return { families, source: "dist/bundles" };
|
|
80067
80125
|
}
|
|
80068
|
-
var boardOpen = (dir) =>
|
|
80126
|
+
var boardOpen = (dir) => existsSync73(join74(dir, ".himi", "studio", "index.html"));
|
|
80069
80127
|
var BOARD_REFRESHERS = /* @__PURE__ */ new Set(["render", "test", "validate", "build", "push", "icon", "site", "ux", "progress"]);
|
|
80070
80128
|
async function leaveForBoard(dir, record2, value) {
|
|
80071
80129
|
if (!boardOpen(dir)) return;
|
|
@@ -80381,7 +80439,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
|
|
|
80381
80439
|
const owed = openApprovals2(readApprovals2(dir).events).length;
|
|
80382
80440
|
next.push(`carried the planning journal into the app (${carried.join(", ")}${owed ? `; ${owed} approval${owed === 1 ? "" : "s"} still owed` : ""}) \u2014 \`himi progress --brief\` there shows it`);
|
|
80383
80441
|
}
|
|
80384
|
-
const planInParent = ["SPEC.md", "MOBILE-UX.md"].filter((f) =>
|
|
80442
|
+
const planInParent = ["SPEC.md", "MOBILE-UX.md"].filter((f) => existsSync73(join74(parentDir, f)));
|
|
80385
80443
|
if (planInParent.length) {
|
|
80386
80444
|
next.push(`${planInParent.join(" and ")} in ${parentDir} ${planInParent.length > 1 ? "are" : "is"} not the app's \u2014 if that is the approved plan, move it over the template's boilerplate in ${dir}`);
|
|
80387
80445
|
}
|
|
@@ -80661,7 +80719,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
|
|
|
80661
80719
|
const result = { ...outcome.result, ...fromSpec };
|
|
80662
80720
|
const adoptDir = contentDir(flags);
|
|
80663
80721
|
const kept = flags["dry-run"] !== true && (outcome.code === 0 || outcome.result.forcedKeep === true);
|
|
80664
|
-
if (kept &&
|
|
80722
|
+
if (kept && existsSync73(join74(adoptDir, adoptBits.ADOPT_FILE))) {
|
|
80665
80723
|
try {
|
|
80666
80724
|
const inv = adoptBits.inspectApp(adoptDir);
|
|
80667
80725
|
adoptBits.writeInventory(adoptDir, inv, { companions: "if-missing" });
|
|
@@ -81225,7 +81283,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
|
|
|
81225
81283
|
case "studio": {
|
|
81226
81284
|
const { openStudio: openStudio2 } = await Promise.resolve().then(() => (init_studio(), studio_exports));
|
|
81227
81285
|
const dir = contentDir(flags);
|
|
81228
|
-
if (!
|
|
81286
|
+
if (!existsSync73(join74(dir, "himalaya.content.json"))) {
|
|
81229
81287
|
io.error(JSON.stringify({ ok: false, error: "not_a_content_package", dir, hint: "run from the app directory (or pass --content <dir>) \u2014 the board reads that package's .himi/" }));
|
|
81230
81288
|
return 2;
|
|
81231
81289
|
}
|
|
@@ -81303,7 +81361,7 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
|
|
|
81303
81361
|
} = await Promise.resolve().then(() => (init_progress(), progress_exports));
|
|
81304
81362
|
const dir = contentDir(flags);
|
|
81305
81363
|
const journal2 = journalPath2(dir);
|
|
81306
|
-
const isContentPackage =
|
|
81364
|
+
const isContentPackage = existsSync73(join74(dir, "himalaya.content.json"));
|
|
81307
81365
|
const hint = isContentPackage ? {} : { hint: "no himalaya.content.json here \u2014 run from the app directory (or pass --content <dir>) so the build board finds this journal" };
|
|
81308
81366
|
const message = pos.slice(1).join(" ").trim();
|
|
81309
81367
|
const approvalFlags = ["approve", "done", "drop"].filter((f) => flags[f] !== void 0);
|
|
@@ -81455,7 +81513,7 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
|
|
|
81455
81513
|
case "test": {
|
|
81456
81514
|
const dir = contentDir(flags);
|
|
81457
81515
|
const structuredOutput = flags.json === true || io.isTTY !== true;
|
|
81458
|
-
if (
|
|
81516
|
+
if (existsSync73(join74(dir, "functions"))) {
|
|
81459
81517
|
const { invokeLocalFunction: invokeLocalFunction2, loadFunctionsApp: loadFunctionsApp2 } = await Promise.resolve().then(() => (init_functions(), functions_exports));
|
|
81460
81518
|
const loaded = await loadFunctionsApp2(dir);
|
|
81461
81519
|
const results = [];
|
|
@@ -82386,7 +82444,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
|
|
|
82386
82444
|
io.error(JSON.stringify(linked));
|
|
82387
82445
|
return 1;
|
|
82388
82446
|
}
|
|
82389
|
-
if (
|
|
82447
|
+
if (existsSync73(dest)) {
|
|
82390
82448
|
if (!statSync20(dest).isDirectory()) {
|
|
82391
82449
|
io.error(JSON.stringify({
|
|
82392
82450
|
error: `${dest} exists and is not a directory`,
|
|
@@ -82399,7 +82457,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
|
|
|
82399
82457
|
io.error(JSON.stringify({ error: `${dest} already exists and is not empty`, hint: "pass --force to write into it anyway (merging, and naming what it keeps), --replace to restore exactly what was archived, or --out/--name to land somewhere else" }));
|
|
82400
82458
|
return 1;
|
|
82401
82459
|
}
|
|
82402
|
-
if (replace && occupied && !
|
|
82460
|
+
if (replace && occupied && !existsSync73(join74(dest, "himalaya.content.json"))) {
|
|
82403
82461
|
io.error(JSON.stringify({
|
|
82404
82462
|
error: `${dest} is not a content package \u2014 refusing to --replace it`,
|
|
82405
82463
|
hint: "--replace only clears a directory that holds himalaya.content.json. Check --out/--name, or pass --force to merge into it instead."
|
|
@@ -84162,7 +84220,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
|
|
|
84162
84220
|
io.error(JSON.stringify({ error: `patch ${kind} needs --file`, usage: PATCH_USAGE }));
|
|
84163
84221
|
return 2;
|
|
84164
84222
|
}
|
|
84165
|
-
if (!
|
|
84223
|
+
if (!existsSync73(file)) {
|
|
84166
84224
|
io.error(JSON.stringify({ error: `no such file: ${file}` }));
|
|
84167
84225
|
return 2;
|
|
84168
84226
|
}
|
|
@@ -84885,7 +84943,7 @@ ${label2}`);
|
|
|
84885
84943
|
const platform = str9(flags.platform)?.split(",").map((p) => p.trim()).filter(Boolean);
|
|
84886
84944
|
{
|
|
84887
84945
|
const distDir = contentDir(flags);
|
|
84888
|
-
if (
|
|
84946
|
+
if (existsSync73(join74(distDir, "himalaya.content.json"))) {
|
|
84889
84947
|
const issues = await iconIssues(distDir, flags.strict === true, { requireDeclared: true, requireVerified: true });
|
|
84890
84948
|
const errors = issues.filter((i) => i.severity === "error");
|
|
84891
84949
|
for (const i of issues.filter((i2) => i2.severity === "warning")) {
|
package/guides/himi-skill.md
CHANGED
|
@@ -450,6 +450,14 @@ I want to create a mobile app for my business. Follow this skill:
|
|
|
450
450
|
https://apps.wix.com/_api/himi-serve/skill.md
|
|
451
451
|
```
|
|
452
452
|
|
|
453
|
+
## Home-screen widgets
|
|
454
|
+
|
|
455
|
+
Read `himi docs widgets` **before** you write a `defineWidget`. A widget is a frozen poster frame:
|
|
456
|
+
its `render` runs once, at build time, over each service's **initial** `state`. Actions, `fetch` and
|
|
457
|
+
Wix data never reach it, and there is no live-data path yet, so a widget written over fetched data
|
|
458
|
+
ships its placeholder. Design it around initial state, constants or a `timeline`, and deep-link the
|
|
459
|
+
tap into the screen that has the live data.
|
|
460
|
+
|
|
453
461
|
## Native UX evidence gate
|
|
454
462
|
|
|
455
463
|
Before declaring a multi-screen app ready, run `himi ux audit --content . --strict` and resolve its findings. After driving each available native shell, persist the actual checks with `himi ux record`; record an unavailable platform with its reason rather than claiming coverage.
|
package/guides/index.json
CHANGED
|
@@ -190,6 +190,12 @@
|
|
|
190
190
|
"category": "Workers & SDKs",
|
|
191
191
|
"file": "cms-collections.md"
|
|
192
192
|
},
|
|
193
|
+
{
|
|
194
|
+
"id": "widgets",
|
|
195
|
+
"title": "Home-screen widgets (what a widget can show)",
|
|
196
|
+
"category": "Workers & SDKs",
|
|
197
|
+
"file": "widgets.md"
|
|
198
|
+
},
|
|
193
199
|
{
|
|
194
200
|
"id": "wix-sdk-overview",
|
|
195
201
|
"title": "Wix SDK overview (front door)",
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# Home-screen widgets
|
|
2
|
+
|
|
3
|
+
A widget is a **frozen poster frame** of your app. It is drawn by the operating system on a home
|
|
4
|
+
screen, lock screen or watch face, and opened with a tap. Your worker never runs
|
|
5
|
+
inside it. This page explains where a widget's content comes from, because that is the part that
|
|
6
|
+
surprises people: **a widget shows what your worker's state is at build time, not what the worker
|
|
7
|
+
later fetches.**
|
|
8
|
+
|
|
9
|
+
## The one rule: a widget renders initial state, once
|
|
10
|
+
|
|
11
|
+
You declare a widget with `defineWidget` in the same worker file as your `defineService`:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { defineWidget } from "@wix/himalaya/widget";
|
|
15
|
+
|
|
16
|
+
defineWidget("<widget id>", {
|
|
17
|
+
family: "ios_home", // which OS surface — see the table below
|
|
18
|
+
size: "small",
|
|
19
|
+
deepLink: "<your app scheme>://screen/<screen id>",
|
|
20
|
+
refresh: { ttlSeconds: 900 },
|
|
21
|
+
render: (services) => ({ /* a component tree of literals */ }),
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
When your app is built or served, the platform **produces** each widget:
|
|
26
|
+
|
|
27
|
+
1. It evaluates your worker in a sandbox and records each `defineService`'s **initial `state`**.
|
|
28
|
+
2. It calls `render(services)` **once, synchronously**, where `services` is
|
|
29
|
+
`{ <service name>: <its initial state> }`.
|
|
30
|
+
3. It freezes the tree `render` returned and serves it as the widget's snapshot.
|
|
31
|
+
|
|
32
|
+
So, inside that sandbox:
|
|
33
|
+
|
|
34
|
+
| What your worker does | What the widget sees |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `state: { … }` in `defineService` | ✅ exactly that object |
|
|
37
|
+
| Constants and plain functions in the file | ✅ available to `render` |
|
|
38
|
+
| An action (`load`, `refresh`, …), sync or `async` | ❌ never runs — the widget keeps the initial state |
|
|
39
|
+
| `fetch`, Wix data queries, any Wix SDK call | ❌ `fetch` throws; nothing it would return arrives |
|
|
40
|
+
| `setTimeout` / `setInterval` | ❌ never fire |
|
|
41
|
+
| `$service:` / `$item` bindings in the tree | ❌ rejected — the snapshot must be literals |
|
|
42
|
+
|
|
43
|
+
`render` must therefore be **synchronous and side-effect free**: no network, no awaiting, no
|
|
44
|
+
bindings, only **resolved literal values**. Reading the clock is the one allowed dependency.
|
|
45
|
+
`Date.now()` works and is evaluated at produce time, so a value computed from it is as fresh as
|
|
46
|
+
the last produce (see [refresh](#refresh-taps-and-previews)). A `render` that throws, or returns a
|
|
47
|
+
tree with a binding in it, makes that widget fail to produce.
|
|
48
|
+
|
|
49
|
+
### There is no live-data path yet
|
|
50
|
+
|
|
51
|
+
Data your worker fetches **cannot** reach a widget today. `refresh.pushTopic` exists in the type
|
|
52
|
+
definitions, but nothing consumes it yet: setting it does not make the widget update when data
|
|
53
|
+
changes. Plan the widget around what you know when the app is built — a call to action, a
|
|
54
|
+
constant, or a schedule (see [timelines](#content-whose-future-you-know-live-time-and-timelines)) — and let the
|
|
55
|
+
tap take the user into the app, where the live data is.
|
|
56
|
+
|
|
57
|
+
## A widget that stays inside the contract
|
|
58
|
+
|
|
59
|
+
This widget shows the studio's name and a fixed call to action, both from initial state. Tapping
|
|
60
|
+
it opens the booking screen, which loads the real schedule.
|
|
61
|
+
|
|
62
|
+
<!-- widget-example: within-contract -->
|
|
63
|
+
```ts
|
|
64
|
+
import { defineService } from "@wix/himalaya/state";
|
|
65
|
+
import { defineWidget } from "@wix/himalaya/widget";
|
|
66
|
+
|
|
67
|
+
defineService("studio", {
|
|
68
|
+
state: { name: "Harbor Yoga", cta: "Book your next class" },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
defineWidget("studio.book", {
|
|
72
|
+
family: "ios_home",
|
|
73
|
+
size: "small",
|
|
74
|
+
deepLink: "harboryoga://screen/book",
|
|
75
|
+
refresh: { ttlSeconds: 86400 },
|
|
76
|
+
render: (services) => {
|
|
77
|
+
const studio = services.studio as { name: string; cta: string };
|
|
78
|
+
return {
|
|
79
|
+
id: "root",
|
|
80
|
+
body: { package: "himalaya", type: "VStack", props: { spacing: 4, children: [
|
|
81
|
+
{ id: "name", body: { package: "himalaya", type: "Text", props: { text: studio.name, style: "headline" } } },
|
|
82
|
+
{ id: "cta", body: { package: "himalaya", type: "Text", props: { text: studio.cta, style: "caption" } } },
|
|
83
|
+
] } },
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
It produces exactly this frozen tree:
|
|
90
|
+
|
|
91
|
+
<!-- widget-example: within-contract.payload -->
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"id": "root",
|
|
95
|
+
"body": { "package": "himalaya", "type": "VStack", "props": { "spacing": 4, "children": [
|
|
96
|
+
{ "id": "name", "body": { "package": "himalaya", "type": "Text", "props": { "text": "Harbor Yoga", "style": "headline" } } },
|
|
97
|
+
{ "id": "cta", "body": { "package": "himalaya", "type": "Text", "props": { "text": "Book your next class", "style": "caption" } } }
|
|
98
|
+
] } }
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## The mistake: a widget over fetched data
|
|
103
|
+
|
|
104
|
+
This is the natural way to write it, and it ships the placeholder:
|
|
105
|
+
|
|
106
|
+
<!-- widget-example: anti-pattern -->
|
|
107
|
+
```ts
|
|
108
|
+
import { defineService } from "@wix/himalaya/state";
|
|
109
|
+
import { defineWidget } from "@wix/himalaya/widget";
|
|
110
|
+
|
|
111
|
+
defineService("latest", {
|
|
112
|
+
state: { title: "Loading…" },
|
|
113
|
+
actions: {
|
|
114
|
+
async load(s) {
|
|
115
|
+
const res = await fetch("https://example.com/latest.json");
|
|
116
|
+
const { title } = (await res.json()) as { title: string };
|
|
117
|
+
return { ...s, title };
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
defineWidget("latest.story", {
|
|
123
|
+
family: "ios_home",
|
|
124
|
+
size: "small",
|
|
125
|
+
deepLink: "myapp://screen/home",
|
|
126
|
+
refresh: { ttlSeconds: 900 },
|
|
127
|
+
render: (services) => ({
|
|
128
|
+
id: "w",
|
|
129
|
+
body: { package: "himalaya", type: "Text", props: { text: (services.latest as { title: string }).title } },
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`load` never runs at produce time, so `render` reads the initial state and the widget says
|
|
135
|
+
"Loading…" forever:
|
|
136
|
+
|
|
137
|
+
<!-- widget-example: anti-pattern.payload -->
|
|
138
|
+
```json
|
|
139
|
+
{ "id": "w", "body": { "package": "himalaya", "type": "Text", "props": { "text": "Loading…" } } }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Calling the action yourself at the top level (`svc.load()`) does not help either: at produce time
|
|
143
|
+
every action resolves to the initial state.
|
|
144
|
+
|
|
145
|
+
## Content whose future you know: live time and timelines
|
|
146
|
+
|
|
147
|
+
**Countdowns and clock text: `Text.timeRef`.** A `Text` with
|
|
148
|
+
`timeRef: { at: <epoch seconds>, style: "timer" | "relative" | "date" | "time" | "offset" }`
|
|
149
|
+
shows a time value that overrides the `Text`'s `text`. How live it is depends on the platform:
|
|
150
|
+
|
|
151
|
+
- **iPhone (home, lock, Live Activity)**: the OS ticks it every second, with no refresh and no worker.
|
|
152
|
+
- **Android home screen**: it is formatted only when the widget is redrawn, which the OS
|
|
153
|
+
schedules (typically no more often than every 15 minutes), so do not rely on it to count seconds.
|
|
154
|
+
- **`web_card`**: `timeRef` is ignored and the `Text`'s own `text` is shown, so always set a
|
|
155
|
+
`text` fallback.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
{ id: "starts", body: { package: "himalaya", type: "Text",
|
|
159
|
+
props: { text: "Starts soon", timeRef: { at: Math.floor(Date.now() / 1000) + 3600, style: "timer" } } } }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Discrete future states: `timeline`.** When you know in advance which *different content* the
|
|
163
|
+
widget should show later — today's agenda, a streak that resets at midnight — add a `timeline`. It returns frozen frames, each with the epoch
|
|
164
|
+
second it becomes current, and the OS advances through them with no network and no worker:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// frame(title, time) returns a component tree; DAY_START is today's midnight in epoch seconds.
|
|
168
|
+
defineWidget("studio.agenda", {
|
|
169
|
+
family: "ios_home",
|
|
170
|
+
size: "small",
|
|
171
|
+
deepLink: "harboryoga://screen/schedule",
|
|
172
|
+
refresh: { ttlSeconds: 900 },
|
|
173
|
+
render: () => frame("Morning flow", "7:00"), // also frame 0 for older clients
|
|
174
|
+
timeline: () => [
|
|
175
|
+
{ startsAt: DAY_START + 7 * 3600, component: frame("Morning flow", "7:00") },
|
|
176
|
+
{ startsAt: DAY_START + 12 * 3600, component: frame("Lunch stretch", "12:00") },
|
|
177
|
+
],
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`timeline` follows the same rule as `render`: synchronous, no network, literals only. It is
|
|
182
|
+
evaluated at produce time, so its frames are only as current as the last produce, and it keeps at
|
|
183
|
+
most 48 frames — never bake a second-by-second countdown into it; that is what `timeRef` is for.
|
|
184
|
+
|
|
185
|
+
## Families and sizes
|
|
186
|
+
|
|
187
|
+
| `family` | Surface |
|
|
188
|
+
|---|---|
|
|
189
|
+
| `ios_home` | iPhone home screen |
|
|
190
|
+
| `ios_lock` | iPhone lock screen |
|
|
191
|
+
| `android_home` | Android home screen |
|
|
192
|
+
| `web_card` | A card on the web preview |
|
|
193
|
+
| `ios_live_activity` | iPhone Live Activity |
|
|
194
|
+
| `watch_complication` | Watch face complication |
|
|
195
|
+
|
|
196
|
+
`size` is the OS size hint: `small`, `medium` or `large` for home-screen widgets, and `inline`,
|
|
197
|
+
`circular` or `rectangular` for lock-screen and complication slots.
|
|
198
|
+
|
|
199
|
+
One worker can declare several widgets, one per family and size. To give a widget a *different
|
|
200
|
+
layout* from your phone screen while reading the same services, use `defineView` with a
|
|
201
|
+
`widget:<family>` surface instead. It is the same frozen contract, chosen per surface, and
|
|
202
|
+
`size`, `deepLink`, `refresh` and `timeline` are accepted only on those frozen surfaces.
|
|
203
|
+
|
|
204
|
+
**In-car screens are not a widget family.** The car display is authored with
|
|
205
|
+
`defineView("<view id>", { surface: "car", deepLink, refresh, render })`: frozen like a widget,
|
|
206
|
+
with the same rules for `render`. `deepLink` and `refresh` are required, as for a widget; `size` is
|
|
207
|
+
not, because the car lays out its own rows. It never goes through `defineWidget`.
|
|
208
|
+
|
|
209
|
+
## Refresh, taps and previews
|
|
210
|
+
|
|
211
|
+
- **`refresh.ttlSeconds`** is how long a snapshot may be served before it is produced again. A new
|
|
212
|
+
produce still renders the *initial* state, so a shorter TTL only helps content that is computed
|
|
213
|
+
from the clock at produce time. It does not make fetched data appear.
|
|
214
|
+
- **`refresh.preferredRefreshSeconds`** (optional) is a hint to the OS background-refresh
|
|
215
|
+
scheduler about how often to redraw the widget; when unset, `ttlSeconds` is used. The OS may
|
|
216
|
+
clamp or defer it (Android never redraws more often than every 15 minutes), so treat it as a
|
|
217
|
+
request, not a schedule.
|
|
218
|
+
- **`deepLink`** is where a tap takes the user. Point it at the screen that shows the live version
|
|
219
|
+
of what the widget summarises.
|
|
220
|
+
- **Preview a widget** while you work: `himi run --watch --content .` serves it at
|
|
221
|
+
`GET /v1/widget/<widget id>`, and `GET /v1/widgets` lists every widget the app produces.
|
|
222
|
+
`himi surfaces --measure-widgets` reports which families your package actually produces.
|
|
223
|
+
|
|
224
|
+
## What `himi validate` checks
|
|
225
|
+
|
|
226
|
+
A widget is **declared** when a worker calls `defineWidget` (or `defineView` with a `widget:`
|
|
227
|
+
surface). If a declared widget fails to produce — `render` threw, returned a binding, or used a
|
|
228
|
+
component its family does not allow — `himi validate` reports `widget-not-produced`. It is a
|
|
229
|
+
warning by default and an **error under `--strict`**, so a package cannot ship a widget it
|
|
230
|
+
declared but that renders nothing.
|
|
231
|
+
|
|
232
|
+
A widget that produces the *placeholder* (the mistake above) is not an error: it renders, it just
|
|
233
|
+
shows initial state. Check the snapshot at `GET /v1/widget/<widget id>` before you ship.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1158.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
|
|
6
6
|
"keywords": [
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"artifactId": "himalaya-cli"
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
|
-
"falconPackageHash": "
|
|
70
|
+
"falconPackageHash": "e2949fb643b50db7dff29f93ca7ee3235cd80cdac4a32d59ba2f89a1"
|
|
71
71
|
}
|