@wix/himalaya-cli 0.1424.0 → 0.1426.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/cli.mjs
CHANGED
|
@@ -10225,6 +10225,115 @@ var init_raw_json_depth = __esm({
|
|
|
10225
10225
|
}
|
|
10226
10226
|
});
|
|
10227
10227
|
|
|
10228
|
+
// ../../core/dev-server/src/depth-budget.ts
|
|
10229
|
+
function isOpaqueAuthorJson(key2, value, atRoot) {
|
|
10230
|
+
if (value === null || typeof value !== "object") return false;
|
|
10231
|
+
return key2 === "args" || atRoot && key2 === "input";
|
|
10232
|
+
}
|
|
10233
|
+
function depthBudgetFindings(descriptor) {
|
|
10234
|
+
const out2 = [];
|
|
10235
|
+
const templates = [];
|
|
10236
|
+
const isComponent = (v2) => {
|
|
10237
|
+
const body = v2?.body;
|
|
10238
|
+
return !!body && typeof body === "object" && typeof body.type === "string" && typeof body.package === "string";
|
|
10239
|
+
};
|
|
10240
|
+
const hostedTemplates = (v2, path2) => {
|
|
10241
|
+
const props = v2.body.props;
|
|
10242
|
+
const found = [];
|
|
10243
|
+
if (!props) return found;
|
|
10244
|
+
const itemsPath = props.itemsPath ?? props.items_path;
|
|
10245
|
+
const rowHost = v2.body.package === "himalaya" ? ROW_TEMPLATE_HOST_TYPES.has(v2.body.type) : true;
|
|
10246
|
+
if (rowHost && typeof itemsPath === "string" && itemsPath !== "" && Array.isArray(props.items) && isComponent(props.items[0])) {
|
|
10247
|
+
found.push({ node: props.items[0], path: `${path2}.body.props.items[0]`, host: `${v2.body.type} at ${path2}` });
|
|
10248
|
+
}
|
|
10249
|
+
if (isComponent(props.eventTemplate)) {
|
|
10250
|
+
found.push({ node: props.eventTemplate, path: `${path2}.body.props.eventTemplate`, host: `${v2.body.type} at ${path2}` });
|
|
10251
|
+
}
|
|
10252
|
+
return found;
|
|
10253
|
+
};
|
|
10254
|
+
const measure2 = (root, rootPath, mode) => {
|
|
10255
|
+
let depth = 0, nodes = 0, deepest = rootPath;
|
|
10256
|
+
const skip = /* @__PURE__ */ new Set();
|
|
10257
|
+
const stack = [
|
|
10258
|
+
{ v: root, path: rootPath, depth: 0, atRoot: mode === "screen" }
|
|
10259
|
+
];
|
|
10260
|
+
while (stack.length) {
|
|
10261
|
+
const { v: v2, path: path2, depth: parentDepth, atRoot } = stack.pop();
|
|
10262
|
+
if (skip.has(v2)) continue;
|
|
10263
|
+
if (Array.isArray(v2)) {
|
|
10264
|
+
for (let i2 = v2.length - 1; i2 >= 0; i2--) stack.push({ v: v2[i2], path: `${path2}[${i2}]`, depth: parentDepth, atRoot: false });
|
|
10265
|
+
continue;
|
|
10266
|
+
}
|
|
10267
|
+
if (!v2 || typeof v2 !== "object") continue;
|
|
10268
|
+
let here = parentDepth;
|
|
10269
|
+
if (isComponent(v2)) {
|
|
10270
|
+
here = parentDepth + 1;
|
|
10271
|
+
nodes += 1;
|
|
10272
|
+
if (here > depth) {
|
|
10273
|
+
depth = here;
|
|
10274
|
+
deepest = path2;
|
|
10275
|
+
}
|
|
10276
|
+
for (const t2 of hostedTemplates(v2, path2)) {
|
|
10277
|
+
if (mode === "screen") templates.push(t2);
|
|
10278
|
+
else skip.add(t2.node);
|
|
10279
|
+
}
|
|
10280
|
+
}
|
|
10281
|
+
const entries = Object.entries(v2);
|
|
10282
|
+
for (let i2 = entries.length - 1; i2 >= 0; i2--) {
|
|
10283
|
+
const [key2, child] = entries[i2];
|
|
10284
|
+
if (!child || typeof child !== "object" || isOpaqueAuthorJson(key2, child, atRoot)) continue;
|
|
10285
|
+
stack.push({ v: child, path: `${path2}.${key2}`, depth: here, atRoot: false });
|
|
10286
|
+
}
|
|
10287
|
+
}
|
|
10288
|
+
return { depth, nodes, deepest };
|
|
10289
|
+
};
|
|
10290
|
+
const flatten = "flatten the layout \u2014 drop wrapper stacks, or move a section into its own screen";
|
|
10291
|
+
const report3 = (what, m2, budget, rootPath) => {
|
|
10292
|
+
if (m2.depth > budget.maxDepth) {
|
|
10293
|
+
out2.push({
|
|
10294
|
+
measure: "depth",
|
|
10295
|
+
path: m2.deepest,
|
|
10296
|
+
limit: budget.maxDepth,
|
|
10297
|
+
actual: m2.depth,
|
|
10298
|
+
message: `${what} nests ${m2.depth} component levels; the limit is ${budget.maxDepth} (deepest node at ${m2.deepest}) \u2014 ${flatten}`
|
|
10299
|
+
});
|
|
10300
|
+
}
|
|
10301
|
+
if (m2.nodes > budget.maxNodes) {
|
|
10302
|
+
out2.push({
|
|
10303
|
+
measure: "nodes",
|
|
10304
|
+
path: rootPath,
|
|
10305
|
+
limit: budget.maxNodes,
|
|
10306
|
+
actual: m2.nodes,
|
|
10307
|
+
message: `${what} has ${m2.nodes} components; the limit is ${budget.maxNodes} \u2014 ${flatten}`
|
|
10308
|
+
});
|
|
10309
|
+
}
|
|
10310
|
+
};
|
|
10311
|
+
report3("screen", measure2(descriptor, "$", "screen"), DESCRIPTOR_DEPTH_BUDGETS.screen, "$");
|
|
10312
|
+
for (const t2 of templates) {
|
|
10313
|
+
report3(`row template of the ${t2.host}`, measure2(t2.node, t2.path, "template"), DESCRIPTOR_DEPTH_BUDGETS.rowTemplate, t2.path);
|
|
10314
|
+
}
|
|
10315
|
+
return out2;
|
|
10316
|
+
}
|
|
10317
|
+
function screenDepthFindings(descriptor) {
|
|
10318
|
+
const raw = rawJsonDepthFinding(descriptor);
|
|
10319
|
+
return raw ? [raw] : depthBudgetFindings(descriptor);
|
|
10320
|
+
}
|
|
10321
|
+
function depthBudgetIssue(f2) {
|
|
10322
|
+
return { severity: "error", pass: "depth-budget", path: f2.path, message: f2.message, limit: f2.limit, actual: f2.actual };
|
|
10323
|
+
}
|
|
10324
|
+
var DESCRIPTOR_DEPTH_BUDGETS, ROW_TEMPLATE_HOST_TYPES;
|
|
10325
|
+
var init_depth_budget = __esm({
|
|
10326
|
+
"../../core/dev-server/src/depth-budget.ts"() {
|
|
10327
|
+
init_worker_subtree();
|
|
10328
|
+
init_raw_json_depth();
|
|
10329
|
+
DESCRIPTOR_DEPTH_BUDGETS = Object.freeze({
|
|
10330
|
+
screen: Object.freeze({ maxDepth: 40, maxNodes: 2e3 }),
|
|
10331
|
+
rowTemplate: Object.freeze({ maxDepth: WORKER_SUBTREE_CEILINGS.maxDepth, maxNodes: WORKER_SUBTREE_CEILINGS.maxNodes })
|
|
10332
|
+
});
|
|
10333
|
+
ROW_TEMPLATE_HOST_TYPES = /* @__PURE__ */ new Set(["LazyList", "Grid", "Carousel", "Menu"]);
|
|
10334
|
+
}
|
|
10335
|
+
});
|
|
10336
|
+
|
|
10228
10337
|
// ../../core/dev-server/src/realm-parity.ts
|
|
10229
10338
|
function allowlistSweepSource(keep = []) {
|
|
10230
10339
|
const key2 = [...keep].sort().join(",");
|
|
@@ -10302,6 +10411,7 @@ __export(validate_descriptor_core_exports, {
|
|
|
10302
10411
|
collectServiceBindings: () => collectServiceBindings,
|
|
10303
10412
|
componentPropFindings: () => componentPropFindings,
|
|
10304
10413
|
depthBudgetFindings: () => depthBudgetFindings,
|
|
10414
|
+
depthBudgetIssue: () => depthBudgetIssue,
|
|
10305
10415
|
depthBudgetIssues: () => depthBudgetIssues,
|
|
10306
10416
|
deviceParityWithheldFrom: () => deviceParityWithheldFrom,
|
|
10307
10417
|
dryRunBundle: () => dryRunBundle,
|
|
@@ -11094,10 +11204,6 @@ function ellipsis(value, max) {
|
|
|
11094
11204
|
function own3(table, key2) {
|
|
11095
11205
|
return Object.prototype.hasOwnProperty.call(table, key2) ? table[key2] : void 0;
|
|
11096
11206
|
}
|
|
11097
|
-
function isOpaqueAuthorJson(key2, value, atRoot) {
|
|
11098
|
-
if (value === null || typeof value !== "object") return false;
|
|
11099
|
-
return key2 === "args" || atRoot && key2 === "input";
|
|
11100
|
-
}
|
|
11101
11207
|
function pathCrossesOpaqueAuthorJson(path2) {
|
|
11102
11208
|
if (typeof path2 !== "string") return false;
|
|
11103
11209
|
return /^\$\.input(?:[.[]|$)/.test(path2) || /\.args(?:[.[]|$)/.test(path2);
|
|
@@ -11828,103 +11934,8 @@ function bottomSheetFindings(descriptor) {
|
|
|
11828
11934
|
visit(descriptor, "", true);
|
|
11829
11935
|
return out2;
|
|
11830
11936
|
}
|
|
11831
|
-
function screenDepthFindings(descriptor) {
|
|
11832
|
-
const raw = rawJsonDepthFinding(descriptor);
|
|
11833
|
-
return raw ? [raw] : depthBudgetFindings(descriptor);
|
|
11834
|
-
}
|
|
11835
11937
|
function depthBudgetIssues(findings) {
|
|
11836
|
-
return findings.map(
|
|
11837
|
-
severity: "error",
|
|
11838
|
-
pass: "depth-budget",
|
|
11839
|
-
path: f2.path,
|
|
11840
|
-
message: f2.message,
|
|
11841
|
-
limit: f2.limit,
|
|
11842
|
-
actual: f2.actual
|
|
11843
|
-
}));
|
|
11844
|
-
}
|
|
11845
|
-
function depthBudgetFindings(descriptor) {
|
|
11846
|
-
const out2 = [];
|
|
11847
|
-
const templates = [];
|
|
11848
|
-
const isComponent = (v2) => {
|
|
11849
|
-
const body = v2?.body;
|
|
11850
|
-
return !!body && typeof body === "object" && typeof body.type === "string" && typeof body.package === "string";
|
|
11851
|
-
};
|
|
11852
|
-
const hostedTemplates = (v2, path2) => {
|
|
11853
|
-
const props = v2.body.props;
|
|
11854
|
-
const found = [];
|
|
11855
|
-
if (!props) return found;
|
|
11856
|
-
const itemsPath = props.itemsPath ?? props.items_path;
|
|
11857
|
-
const rowHost = v2.body.package === "himalaya" ? ROW_TEMPLATE_HOST_TYPES.has(v2.body.type) : true;
|
|
11858
|
-
if (rowHost && typeof itemsPath === "string" && itemsPath !== "" && Array.isArray(props.items) && isComponent(props.items[0])) {
|
|
11859
|
-
found.push({ node: props.items[0], path: `${path2}.body.props.items[0]`, host: `${v2.body.type} at ${path2}` });
|
|
11860
|
-
}
|
|
11861
|
-
if (isComponent(props.eventTemplate)) {
|
|
11862
|
-
found.push({ node: props.eventTemplate, path: `${path2}.body.props.eventTemplate`, host: `${v2.body.type} at ${path2}` });
|
|
11863
|
-
}
|
|
11864
|
-
return found;
|
|
11865
|
-
};
|
|
11866
|
-
const measure2 = (root, rootPath, mode) => {
|
|
11867
|
-
let depth = 0, nodes = 0, deepest = rootPath;
|
|
11868
|
-
const skip = /* @__PURE__ */ new Set();
|
|
11869
|
-
const stack = [
|
|
11870
|
-
{ v: root, path: rootPath, depth: 0, atRoot: mode === "screen" }
|
|
11871
|
-
];
|
|
11872
|
-
while (stack.length) {
|
|
11873
|
-
const { v: v2, path: path2, depth: parentDepth, atRoot } = stack.pop();
|
|
11874
|
-
if (skip.has(v2)) continue;
|
|
11875
|
-
if (Array.isArray(v2)) {
|
|
11876
|
-
for (let i2 = v2.length - 1; i2 >= 0; i2--) stack.push({ v: v2[i2], path: `${path2}[${i2}]`, depth: parentDepth, atRoot: false });
|
|
11877
|
-
continue;
|
|
11878
|
-
}
|
|
11879
|
-
if (!v2 || typeof v2 !== "object") continue;
|
|
11880
|
-
let here = parentDepth;
|
|
11881
|
-
if (isComponent(v2)) {
|
|
11882
|
-
here = parentDepth + 1;
|
|
11883
|
-
nodes += 1;
|
|
11884
|
-
if (here > depth) {
|
|
11885
|
-
depth = here;
|
|
11886
|
-
deepest = path2;
|
|
11887
|
-
}
|
|
11888
|
-
for (const t2 of hostedTemplates(v2, path2)) {
|
|
11889
|
-
if (mode === "screen") templates.push(t2);
|
|
11890
|
-
else skip.add(t2.node);
|
|
11891
|
-
}
|
|
11892
|
-
}
|
|
11893
|
-
const entries = Object.entries(v2);
|
|
11894
|
-
for (let i2 = entries.length - 1; i2 >= 0; i2--) {
|
|
11895
|
-
const [key2, child] = entries[i2];
|
|
11896
|
-
if (!child || typeof child !== "object" || isOpaqueAuthorJson(key2, child, atRoot)) continue;
|
|
11897
|
-
stack.push({ v: child, path: `${path2}.${key2}`, depth: here, atRoot: false });
|
|
11898
|
-
}
|
|
11899
|
-
}
|
|
11900
|
-
return { depth, nodes, deepest };
|
|
11901
|
-
};
|
|
11902
|
-
const flatten = "flatten the layout \u2014 drop wrapper stacks, or move a section into its own screen";
|
|
11903
|
-
const report3 = (what, m2, budget, rootPath) => {
|
|
11904
|
-
if (m2.depth > budget.maxDepth) {
|
|
11905
|
-
out2.push({
|
|
11906
|
-
measure: "depth",
|
|
11907
|
-
path: m2.deepest,
|
|
11908
|
-
limit: budget.maxDepth,
|
|
11909
|
-
actual: m2.depth,
|
|
11910
|
-
message: `${what} nests ${m2.depth} component levels; the limit is ${budget.maxDepth} (deepest node at ${m2.deepest}) \u2014 ${flatten}`
|
|
11911
|
-
});
|
|
11912
|
-
}
|
|
11913
|
-
if (m2.nodes > budget.maxNodes) {
|
|
11914
|
-
out2.push({
|
|
11915
|
-
measure: "nodes",
|
|
11916
|
-
path: rootPath,
|
|
11917
|
-
limit: budget.maxNodes,
|
|
11918
|
-
actual: m2.nodes,
|
|
11919
|
-
message: `${what} has ${m2.nodes} components; the limit is ${budget.maxNodes} \u2014 ${flatten}`
|
|
11920
|
-
});
|
|
11921
|
-
}
|
|
11922
|
-
};
|
|
11923
|
-
report3("screen", measure2(descriptor, "$", "screen"), DESCRIPTOR_DEPTH_BUDGETS.screen, "$");
|
|
11924
|
-
for (const t2 of templates) {
|
|
11925
|
-
report3(`row template of the ${t2.host}`, measure2(t2.node, t2.path, "template"), DESCRIPTOR_DEPTH_BUDGETS.rowTemplate, t2.path);
|
|
11926
|
-
}
|
|
11927
|
-
return out2;
|
|
11938
|
+
return findings.map(depthBudgetIssue);
|
|
11928
11939
|
}
|
|
11929
11940
|
function topBarFindings(descriptor) {
|
|
11930
11941
|
const out2 = [];
|
|
@@ -12685,7 +12696,7 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
|
|
|
12685
12696
|
services: Object.keys(services)
|
|
12686
12697
|
};
|
|
12687
12698
|
}
|
|
12688
|
-
var CRAWL_BLOCKING_PASSES, BOOT_EVAL_TIMEOUT_MS, DRIFT_PROBES, DEVICE_NETWORK_WITHHELD, DEVICE_STRIPPED_HOST_GLOBALS, DEVICE_WITHHELD, DEVICE_PARITY_CANDIDATES, DEVICE_PARITY_WITHHELD, defaultEvaluator, bootEvaluator, actionEvaluator, NATIVE_PROP_ENUMS, KNOWN_PROPS, IN_ARRAY_BINDING_PROPS, SERVICE_SIGIL, INTEGER_PROPS, NATIVE_REQUIRED_PROPS, bareNestedClass, NATIVE_NESTED_CLASSES, NATIVE_MODIFIERS_CLASS, NATIVE_DESCRIPTOR_CLASS, NATIVE_DESCRIPTOR_NESTED, NATIVE_NESTED_PROPS, LENIENT_TOP_BAR_BLOCKS, MODIFIER_KEYS, INTEGER_MODIFIERS, MODIFIER_KINDS, SCHEMA_SCORED_NODES, CORE_PACKAGES, LEGACY_UNSCORED_KEYS, DYNAMIC_PLACEHOLDER, LITERAL_ONLY_PROPS, SERVICE_ONLY_BINDING_PROPS, WHOLE_VALUE_BINDING, EMBEDDED_BINDING, OPTIONS_BINDING_PROPS, EDITOR_SERVICE_NAME, SHEET_PLACEMENTS,
|
|
12699
|
+
var CRAWL_BLOCKING_PASSES, BOOT_EVAL_TIMEOUT_MS, DRIFT_PROBES, DEVICE_NETWORK_WITHHELD, DEVICE_STRIPPED_HOST_GLOBALS, DEVICE_WITHHELD, DEVICE_PARITY_CANDIDATES, DEVICE_PARITY_WITHHELD, defaultEvaluator, bootEvaluator, actionEvaluator, NATIVE_PROP_ENUMS, KNOWN_PROPS, IN_ARRAY_BINDING_PROPS, SERVICE_SIGIL, INTEGER_PROPS, NATIVE_REQUIRED_PROPS, bareNestedClass, NATIVE_NESTED_CLASSES, NATIVE_MODIFIERS_CLASS, NATIVE_DESCRIPTOR_CLASS, NATIVE_DESCRIPTOR_NESTED, NATIVE_NESTED_PROPS, LENIENT_TOP_BAR_BLOCKS, MODIFIER_KEYS, INTEGER_MODIFIERS, MODIFIER_KINDS, SCHEMA_SCORED_NODES, CORE_PACKAGES, LEGACY_UNSCORED_KEYS, DYNAMIC_PLACEHOLDER, LITERAL_ONLY_PROPS, SERVICE_ONLY_BINDING_PROPS, WHOLE_VALUE_BINDING, EMBEDDED_BINDING, OPTIONS_BINDING_PROPS, EDITOR_SERVICE_NAME, SHEET_PLACEMENTS, UNSAFE_TO_RECURSE_DEPTH, TOP_BAR_TRAILING_LIMIT, TOP_BAR_NON_TRAILING, BUTTON_STYLES, EMPTY_STATE_ACTION_STYLES, EMPTY_STATE_ACTION_SIZES;
|
|
12689
12700
|
var init_validate_descriptor_core = __esm({
|
|
12690
12701
|
"../../core/dev-server/src/validate-descriptor.core.ts"() {
|
|
12691
12702
|
init_validator();
|
|
@@ -12700,7 +12711,7 @@ var init_validate_descriptor_core = __esm({
|
|
|
12700
12711
|
init_media_value();
|
|
12701
12712
|
init_frame_limits();
|
|
12702
12713
|
init_rich_content();
|
|
12703
|
-
|
|
12714
|
+
init_depth_budget();
|
|
12704
12715
|
init_raw_json_depth();
|
|
12705
12716
|
init_realm_parity();
|
|
12706
12717
|
init_realm_parity();
|
|
@@ -13733,12 +13744,7 @@ var init_validate_descriptor_core = __esm({
|
|
|
13733
13744
|
OPTIONS_BINDING_PROPS = /* @__PURE__ */ new Set(["optionsPath"]);
|
|
13734
13745
|
EDITOR_SERVICE_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
13735
13746
|
SHEET_PLACEMENTS = /* @__PURE__ */ new Set(["overlay", "inline"]);
|
|
13736
|
-
DESCRIPTOR_DEPTH_BUDGETS = Object.freeze({
|
|
13737
|
-
screen: Object.freeze({ maxDepth: 40, maxNodes: 2e3 }),
|
|
13738
|
-
rowTemplate: Object.freeze({ maxDepth: WORKER_SUBTREE_CEILINGS.maxDepth, maxNodes: WORKER_SUBTREE_CEILINGS.maxNodes })
|
|
13739
|
-
});
|
|
13740
13747
|
UNSAFE_TO_RECURSE_DEPTH = 128;
|
|
13741
|
-
ROW_TEMPLATE_HOST_TYPES = /* @__PURE__ */ new Set(["LazyList", "Grid", "Carousel", "Menu"]);
|
|
13742
13748
|
TOP_BAR_TRAILING_LIMIT = 2;
|
|
13743
13749
|
TOP_BAR_NON_TRAILING = /* @__PURE__ */ new Set(["leading", "principal", "bottom", "keyboard", "overflow"]);
|
|
13744
13750
|
BUTTON_STYLES = /* @__PURE__ */ new Set(["filled", "tonal", "outlined", "text", "elevated", "bordered", "prominent", "plain", "glass", "primary", "secondary", "tertiary", "link", "destructive"]);
|
|
@@ -14295,6 +14301,7 @@ __export(validate_descriptor_exports, {
|
|
|
14295
14301
|
collectServiceBindings: () => collectServiceBindings,
|
|
14296
14302
|
componentPropFindings: () => componentPropFindings,
|
|
14297
14303
|
depthBudgetFindings: () => depthBudgetFindings,
|
|
14304
|
+
depthBudgetIssue: () => depthBudgetIssue,
|
|
14298
14305
|
depthBudgetIssues: () => depthBudgetIssues,
|
|
14299
14306
|
deviceParityWithheldFrom: () => deviceParityWithheldFrom,
|
|
14300
14307
|
discoverNativeActions: () => discoverNativeActions,
|
|
@@ -132881,6 +132888,28 @@ function refuseTooDeep(screenId, descriptor) {
|
|
|
132881
132888
|
if (finding) throw new DescriptorDepthRefusal(screenId, finding);
|
|
132882
132889
|
return descriptor;
|
|
132883
132890
|
}
|
|
132891
|
+
async function liveDescriptorFor(state, screenId) {
|
|
132892
|
+
try {
|
|
132893
|
+
const override = state.localOverrides.get(screenId);
|
|
132894
|
+
return {
|
|
132895
|
+
descriptor: override !== void 0 ? refuseTooDeep(screenId, override) : await buildDescriptorForScreenId(state, screenId, new URLSearchParams(), { skipOverride: true })
|
|
132896
|
+
};
|
|
132897
|
+
} catch (err) {
|
|
132898
|
+
if (err instanceof DescriptorDepthRefusal) return { refusal: err };
|
|
132899
|
+
throw err;
|
|
132900
|
+
}
|
|
132901
|
+
}
|
|
132902
|
+
function depthRefusalReport(refusal, strict = false) {
|
|
132903
|
+
return { screenId: refusal.screenId, ok: false, strict, issues: [depthBudgetIssue(refusal.finding)], services: [] };
|
|
132904
|
+
}
|
|
132905
|
+
function depthRefusalDiagnostics(state, refusal) {
|
|
132906
|
+
const legacy = { ...depthRefusalReport(refusal), bundle: null, telemetry: [], lastError: null };
|
|
132907
|
+
return foldLegacyReport(legacy, {
|
|
132908
|
+
source: "diagnostics",
|
|
132909
|
+
screenId: refusal.screenId,
|
|
132910
|
+
evidence: { runId: `dev:${state.startedAt}:${refusal.screenId}`, ts: (/* @__PURE__ */ new Date()).toISOString() }
|
|
132911
|
+
});
|
|
132912
|
+
}
|
|
132884
132913
|
async function buildDescriptorForScreenId(state, screenId, query = new URLSearchParams(), options = {}) {
|
|
132885
132914
|
if (!options.skipOverride) {
|
|
132886
132915
|
const preview = state.localOverrides.get(screenId);
|
|
@@ -132910,13 +132939,13 @@ async function buildDescriptorForScreenId(state, screenId, query = new URLSearch
|
|
|
132910
132939
|
}
|
|
132911
132940
|
return null;
|
|
132912
132941
|
})();
|
|
132913
|
-
return built ? postprocessDescriptor(refuseTooDeep(screenId, built), {
|
|
132942
|
+
return built ? refuseTooDeep(screenId, postprocessDescriptor(refuseTooDeep(screenId, built), {
|
|
132914
132943
|
config: uiConfig(state.app.module.config),
|
|
132915
132944
|
flagDefinitions: state.flagStore.list(),
|
|
132916
132945
|
evalContext: evalCtx,
|
|
132917
132946
|
flagOverrides: cfg?.overrides,
|
|
132918
132947
|
readBundle: tier5BundleReader(state.tier5Dir ?? TIER5_ROOT2)
|
|
132919
|
-
}) : null;
|
|
132948
|
+
})) : null;
|
|
132920
132949
|
}
|
|
132921
132950
|
return null;
|
|
132922
132951
|
}
|
|
@@ -134156,7 +134185,21 @@ async function handleDev(req, res, state) {
|
|
|
134156
134185
|
}
|
|
134157
134186
|
let descriptor = body.descriptor;
|
|
134158
134187
|
if (!descriptor && body.screenId) {
|
|
134159
|
-
|
|
134188
|
+
const live3 = await liveDescriptorFor(state, body.screenId);
|
|
134189
|
+
if ("refusal" in live3) {
|
|
134190
|
+
const diagnostics2 = depthRefusalDiagnostics(state, live3.refusal);
|
|
134191
|
+
const legacy2 = { screenId: body.screenId, scheme, renderable: false, reason: `depth_budget_exceeded: ${live3.refusal.finding.message}`, diagnostics: diagnostics2 };
|
|
134192
|
+
const report4 = foldLegacyReport(legacy2, {
|
|
134193
|
+
source: "render",
|
|
134194
|
+
screenId: body.screenId,
|
|
134195
|
+
ok: false,
|
|
134196
|
+
findings: diagnostics2.findings ?? [],
|
|
134197
|
+
artifacts: [],
|
|
134198
|
+
evidence: { runId: `dev:${state.startedAt}:${body.screenId}`, ts: (/* @__PURE__ */ new Date()).toISOString() }
|
|
134199
|
+
});
|
|
134200
|
+
return sendJson(res, body.envelope === 1 ? 422 : 200, report4);
|
|
134201
|
+
}
|
|
134202
|
+
descriptor = live3.descriptor;
|
|
134160
134203
|
}
|
|
134161
134204
|
if (!descriptor || typeof descriptor !== "object") {
|
|
134162
134205
|
return sendJson(res, 404, { error: "screen_not_found", screenId: body.screenId ?? null });
|
|
@@ -134203,7 +134246,9 @@ async function handleDev(req, res, state) {
|
|
|
134203
134246
|
const diagMatch = /^\/__dev\/screen\/(.+?)\/diagnostics(?:\?|$)/.exec(url);
|
|
134204
134247
|
if (diagMatch && req.method === "GET") {
|
|
134205
134248
|
const screenId = decodeURIComponent(diagMatch[1]);
|
|
134206
|
-
const
|
|
134249
|
+
const live3 = await liveDescriptorFor(state, screenId);
|
|
134250
|
+
if ("refusal" in live3) return sendJson(res, 422, depthRefusalDiagnostics(state, live3.refusal));
|
|
134251
|
+
const descriptor = live3.descriptor;
|
|
134207
134252
|
if (!descriptor) return sendJson(res, 404, { error: "screen_not_found", screenId });
|
|
134208
134253
|
const diag = await runScreenDiagnostics(state, screenId, descriptor);
|
|
134209
134254
|
return sendJson(res, diag.ok ? 200 : 422, diag);
|
|
@@ -134645,7 +134690,9 @@ async function handleAdminPlayground(req, res, state) {
|
|
|
134645
134690
|
if (validateMatch && req.method === "GET") {
|
|
134646
134691
|
const screenId = decodeURIComponent(validateMatch[1]);
|
|
134647
134692
|
const strict = /[?&]strict=1(?:&|$)/.test(url);
|
|
134648
|
-
const
|
|
134693
|
+
const live3 = await liveDescriptorFor(state, screenId);
|
|
134694
|
+
if ("refusal" in live3) return sendJson(res, 422, depthRefusalReport(live3.refusal, strict));
|
|
134695
|
+
const descriptor = live3.descriptor;
|
|
134649
134696
|
if (!descriptor) return sendJson(res, 404, { error: "screen_not_found", screenId });
|
|
134650
134697
|
const { validateScreenDescriptor: validateScreenDescriptor2, loadAllowedComponents: loadAllowedComponents2, readBuiltBundle: readBuiltBundle2, appWorkerActionsFor: appWorkerActionsFor2 } = await Promise.resolve().then(() => (init_validate_descriptor(), validate_descriptor_exports));
|
|
134651
134698
|
const tokens2 = readAppTokens(state) ?? void 0;
|
|
@@ -134677,7 +134724,7 @@ async function handleAdminPlayground(req, res, state) {
|
|
|
134677
134724
|
if (!wantBuilt) {
|
|
134678
134725
|
const override = state.localOverrides.get(screenId);
|
|
134679
134726
|
if (override) {
|
|
134680
|
-
return sendJson(res, 200, { screenId, source: "override", descriptor: { ...override, screenId } });
|
|
134727
|
+
return sendJson(res, 200, { screenId, source: "override", descriptor: refuseTooDeep(screenId, { ...override, screenId }) });
|
|
134681
134728
|
}
|
|
134682
134729
|
}
|
|
134683
134730
|
const built = await buildDescriptorForScreenId(state, screenId, new URLSearchParams(), { skipOverride: true });
|
|
@@ -134695,6 +134742,7 @@ async function handleAdminPlayground(req, res, state) {
|
|
|
134695
134742
|
if (!payload.descriptor || typeof payload.descriptor !== "object") {
|
|
134696
134743
|
return sendJson(res, 400, { error: "descriptor_required" });
|
|
134697
134744
|
}
|
|
134745
|
+
refuseTooDeep(screenId, payload.descriptor);
|
|
134698
134746
|
state.localOverrides.set(screenId, payload.descriptor);
|
|
134699
134747
|
recordOverrideHistory(state, screenId, "put", payload.descriptor, payload.notes ?? null);
|
|
134700
134748
|
invalidate(state, screenId);
|
|
@@ -135715,6 +135763,7 @@ var init_src6 = __esm({
|
|
|
135715
135763
|
init_app_icon();
|
|
135716
135764
|
init_app_assets();
|
|
135717
135765
|
init_raw_json_depth();
|
|
135766
|
+
init_depth_budget();
|
|
135718
135767
|
init_app_module();
|
|
135719
135768
|
init_provisioning_facts();
|
|
135720
135769
|
init_validate();
|
|
@@ -139420,6 +139469,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
139420
139469
|
const store = typeof dest === "string" ? new FileReleaseStore(dest) : dest;
|
|
139421
139470
|
const screenIds = Object.keys(preloaded.module.screens.exact);
|
|
139422
139471
|
const failedToBuild = [];
|
|
139472
|
+
const depthRefusals = [];
|
|
139423
139473
|
const missingBundles = [];
|
|
139424
139474
|
const widgetBakeFailures = [];
|
|
139425
139475
|
const lint = [];
|
|
@@ -139801,6 +139851,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
139801
139851
|
const finding = depthRefusalFromText(res.status, text9);
|
|
139802
139852
|
if (finding) {
|
|
139803
139853
|
failedToBuild.push({ screenId, status: res.status, error: finding.message });
|
|
139854
|
+
if (!depthRefusals.some((r2) => r2.screenId === screenId)) depthRefusals.push({ screenId, finding });
|
|
139804
139855
|
if (collect2?.lint && !lint.some((l2) => l2.screenId === screenId)) {
|
|
139805
139856
|
lint.push({ screenId, ok: false, issues: depthBudgetIssues([finding]) });
|
|
139806
139857
|
}
|
|
@@ -139905,10 +139956,11 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
139905
139956
|
const { services, liveCalls, liveReturned, liveErrors, mockCalls, mockUnserved, mockBootFailed, liveFailed } = await renderServicesFor(bundleName, descriptor, wantedRender);
|
|
139906
139957
|
const rr2 = await renderDescriptorWeb(descriptor, renderTokens, renderScheme, collect2.render.png === true, services, collect2.render.width, collect2.render.rtl === true);
|
|
139907
139958
|
renders.push({ screenId: wantedRender, scheme: renderScheme, ...rr2, ...await renderCompare(descriptor, services), ...collect2.render.dumpState && services ? { serviceState: services } : {}, ...liveCalls ? { liveCalls } : {}, ...liveReturned !== void 0 ? { liveReturned } : {}, ...liveFailed ? { liveFailed } : {}, ...liveErrors ? { liveErrors } : {}, ...mockCalls ? { mockCalls, mockUnserved } : {}, ...mockBootFailed ? { mockBootFailed } : {} });
|
|
139908
|
-
} else if (res.status === 422
|
|
139959
|
+
} else if (res.status === 422) {
|
|
139909
139960
|
const finding = await depthRefusalOf(res);
|
|
139961
|
+
if (finding && !depthRefusals.some((r2) => r2.screenId === wantedRender)) depthRefusals.push({ screenId: wantedRender, finding });
|
|
139910
139962
|
const recorded = lint.some((l2) => l2.screenId === wantedRender) || failedToBuild.some((f2) => f2.screenId === wantedRender);
|
|
139911
|
-
if (finding && !recorded) lint.push({ screenId: wantedRender, ok: false, issues: depthBudgetIssues([finding]) });
|
|
139963
|
+
if (finding && collect2?.lint && !recorded) lint.push({ screenId: wantedRender, ok: false, issues: depthBudgetIssues([finding]) });
|
|
139912
139964
|
}
|
|
139913
139965
|
}
|
|
139914
139966
|
for (const t2 of preloaded.module.screens.templated ?? []) {
|
|
@@ -139990,6 +140042,18 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
139990
140042
|
}
|
|
139991
140043
|
delete descriptor.etag;
|
|
139992
140044
|
if (typeof descriptor.screenId === "string" && descriptor.screenId !== screenId) descriptor.screenId = screenId;
|
|
140045
|
+
if (collect2?.lint) {
|
|
140046
|
+
const depth = depthBudgetIssues(screenDepthFindings(descriptor));
|
|
140047
|
+
const existing = lint.find((l2) => l2.screenId === screenId);
|
|
140048
|
+
const seen = new Set((existing?.issues ?? []).filter((i2) => i2.pass === "depth-budget").map((i2) => `${i2.path}|${i2.limit}`));
|
|
140049
|
+
const fresh = depth.filter((i2) => !seen.has(`${i2.path}|${i2.limit}`));
|
|
140050
|
+
if (fresh.length && existing) {
|
|
140051
|
+
existing.issues.push(...fresh);
|
|
140052
|
+
existing.ok = false;
|
|
140053
|
+
} else if (fresh.length) {
|
|
140054
|
+
lint.push({ screenId, ok: false, issues: fresh });
|
|
140055
|
+
}
|
|
140056
|
+
}
|
|
139993
140057
|
const json7 = pinBakedServerBaseUrl(JSON.stringify(descriptor), base);
|
|
139994
140058
|
await store.putScreen(app, screenId, json7);
|
|
139995
140059
|
screens[screenId] = { descriptorHash: sha2564(json7), bundle: bundleName, ...screenMetadataFor(screenMetadata, screenId, canonical4) };
|
|
@@ -140203,6 +140267,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
|
|
|
140203
140267
|
return {
|
|
140204
140268
|
manifest,
|
|
140205
140269
|
failedToBuild,
|
|
140270
|
+
depthRefusals,
|
|
140206
140271
|
missingBundles,
|
|
140207
140272
|
// Only bundles a screen actually referenced are weighed — `tierBundles` is filled by
|
|
140208
140273
|
// addressBundle, which runs on reference, so an unreferenced file on disk never counts.
|
|
@@ -140269,7 +140334,7 @@ async function buildFunctionsOnlyRelease(preloaded, dest) {
|
|
|
140269
140334
|
const sha2 = gitSha();
|
|
140270
140335
|
const manifest = { releaseId: contentReleaseId(content), gitSha: sha2, ...content };
|
|
140271
140336
|
await store.putManifest(app, manifest);
|
|
140272
|
-
return { manifest, failedToBuild: [], missingBundles: [], auth: {}, tierCapViolations: [] };
|
|
140337
|
+
return { manifest, failedToBuild: [], depthRefusals: [], missingBundles: [], auth: {}, tierCapViolations: [] };
|
|
140273
140338
|
}
|
|
140274
140339
|
function listBuildableApps() {
|
|
140275
140340
|
const appsDir = join47(REPO_ROOT7, "apps");
|
|
@@ -163812,6 +163877,8 @@ function createReviewScreenRenderer() {
|
|
|
163812
163877
|
collect: { render: { screenId, scheme: "light", png: false, hydrate: true, live: true, dumpState: true, width: REVIEW_SCREENSHOT_FRAME.width, host, ...input ? { input } : {} } }
|
|
163813
163878
|
});
|
|
163814
163879
|
const render = (outcome.renders ?? [])[0];
|
|
163880
|
+
const refused2 = outcome.depthRefusals.find((r2) => r2.screenId === screenId);
|
|
163881
|
+
if (!render && refused2) throw new Error(`${screenId} cannot be rendered: ${refused2.finding.message}`);
|
|
163815
163882
|
if (!render) throw new MissingScreenError(screenId);
|
|
163816
163883
|
if (!render.renderable || !render.html) throw new Error(render.reason ?? `${screenId} is not an SDUI screen`);
|
|
163817
163884
|
return { html: render.html, problems: renderProblems(render, entityId), state: render.serviceState ?? {} };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1426.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": [
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"artifactId": "himalaya-cli"
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
|
-
"falconPackageHash": "
|
|
74
|
+
"falconPackageHash": "fe8a55204085cb96eb306adea7bc261f10e7605132220f78e2bc4813"
|
|
75
75
|
}
|
|
@@ -34620,71 +34620,294 @@ const SCREENS = runtimeSetting({
|
|
|
34620
34620
|
"itemsPath": "$service:spacesChatThread.messages",
|
|
34621
34621
|
"items": [
|
|
34622
34622
|
{
|
|
34623
|
-
"id": "",
|
|
34623
|
+
"id": "himi_chat_thread.message_$item.id",
|
|
34624
34624
|
"body": {
|
|
34625
34625
|
"package": "himalaya",
|
|
34626
|
-
"type": "
|
|
34626
|
+
"type": "VStack",
|
|
34627
34627
|
"props": {
|
|
34628
|
+
"spacing": 6,
|
|
34628
34629
|
"children": [
|
|
34629
34630
|
{
|
|
34630
34631
|
"id": "",
|
|
34631
34632
|
"body": {
|
|
34632
34633
|
"package": "himalaya",
|
|
34633
|
-
"type": "
|
|
34634
|
+
"type": "HStack",
|
|
34634
34635
|
"props": {
|
|
34635
|
-
"
|
|
34636
|
-
"
|
|
34636
|
+
"spacing": 8,
|
|
34637
|
+
"children": [
|
|
34638
|
+
{
|
|
34639
|
+
"id": "",
|
|
34640
|
+
"body": {
|
|
34641
|
+
"package": "himalaya",
|
|
34642
|
+
"type": "Spacer",
|
|
34643
|
+
"props": {}
|
|
34644
|
+
}
|
|
34645
|
+
},
|
|
34646
|
+
{
|
|
34647
|
+
"id": "",
|
|
34648
|
+
"body": {
|
|
34649
|
+
"package": "himalaya",
|
|
34650
|
+
"type": "Text",
|
|
34651
|
+
"props": {
|
|
34652
|
+
"text": "$item.dayLabel",
|
|
34653
|
+
"style": "caption",
|
|
34654
|
+
"color": "textSecondary",
|
|
34655
|
+
"collapseWhenEmpty": true
|
|
34656
|
+
}
|
|
34657
|
+
}
|
|
34658
|
+
},
|
|
34659
|
+
{
|
|
34660
|
+
"id": "",
|
|
34661
|
+
"body": {
|
|
34662
|
+
"package": "himalaya",
|
|
34663
|
+
"type": "Spacer",
|
|
34664
|
+
"props": {}
|
|
34665
|
+
}
|
|
34666
|
+
}
|
|
34667
|
+
]
|
|
34637
34668
|
}
|
|
34669
|
+
},
|
|
34670
|
+
"modifiers": {
|
|
34671
|
+
"padding": {
|
|
34672
|
+
"leading": 16,
|
|
34673
|
+
"trailing": 16,
|
|
34674
|
+
"top": 16,
|
|
34675
|
+
"bottom": 8
|
|
34676
|
+
},
|
|
34677
|
+
"hiddenPath": "$item.dayHidden"
|
|
34638
34678
|
}
|
|
34639
34679
|
},
|
|
34640
34680
|
{
|
|
34641
34681
|
"id": "",
|
|
34642
34682
|
"body": {
|
|
34643
34683
|
"package": "himalaya",
|
|
34644
|
-
"type": "
|
|
34684
|
+
"type": "HStack",
|
|
34645
34685
|
"props": {
|
|
34646
|
-
"spacing":
|
|
34686
|
+
"spacing": 8,
|
|
34647
34687
|
"alignment": "leading",
|
|
34648
34688
|
"children": [
|
|
34649
34689
|
{
|
|
34650
|
-
"id": "",
|
|
34690
|
+
"id": "himi_chat_thread.incoming_avatar_$item.id",
|
|
34651
34691
|
"body": {
|
|
34652
34692
|
"package": "himalaya",
|
|
34653
|
-
"type": "
|
|
34693
|
+
"type": "Image",
|
|
34654
34694
|
"props": {
|
|
34655
|
-
"
|
|
34656
|
-
"
|
|
34695
|
+
"url": "$item.avatarUrl",
|
|
34696
|
+
"initials": "$item.avatarInitials",
|
|
34697
|
+
"icon": "person",
|
|
34698
|
+
"width": 32,
|
|
34699
|
+
"height": 32,
|
|
34700
|
+
"contentMode": "fill",
|
|
34701
|
+
"shape": "circle"
|
|
34657
34702
|
}
|
|
34703
|
+
},
|
|
34704
|
+
"modifiers": {
|
|
34705
|
+
"accessibilityRole": "decorative"
|
|
34658
34706
|
}
|
|
34659
34707
|
},
|
|
34660
34708
|
{
|
|
34661
34709
|
"id": "",
|
|
34662
34710
|
"body": {
|
|
34663
34711
|
"package": "himalaya",
|
|
34664
|
-
"type": "
|
|
34712
|
+
"type": "VStack",
|
|
34665
34713
|
"props": {
|
|
34666
|
-
"
|
|
34667
|
-
"
|
|
34668
|
-
"
|
|
34669
|
-
|
|
34714
|
+
"spacing": 6,
|
|
34715
|
+
"alignment": "leading",
|
|
34716
|
+
"children": [
|
|
34717
|
+
{
|
|
34718
|
+
"id": "",
|
|
34719
|
+
"body": {
|
|
34720
|
+
"package": "himalaya",
|
|
34721
|
+
"type": "Text",
|
|
34722
|
+
"props": {
|
|
34723
|
+
"text": "$item.senderBadge",
|
|
34724
|
+
"style": "caption",
|
|
34725
|
+
"color": "accentPrimary",
|
|
34726
|
+
"maxLines": 1,
|
|
34727
|
+
"collapseWhenEmpty": true
|
|
34728
|
+
}
|
|
34729
|
+
},
|
|
34730
|
+
"modifiers": {
|
|
34731
|
+
"background": {
|
|
34732
|
+
"token": "accentSurface"
|
|
34733
|
+
},
|
|
34734
|
+
"cornerRadius": 8,
|
|
34735
|
+
"padding": {
|
|
34736
|
+
"leading": 8,
|
|
34737
|
+
"trailing": 8,
|
|
34738
|
+
"top": 4,
|
|
34739
|
+
"bottom": 4
|
|
34740
|
+
}
|
|
34741
|
+
}
|
|
34742
|
+
},
|
|
34743
|
+
{
|
|
34744
|
+
"id": "",
|
|
34745
|
+
"body": {
|
|
34746
|
+
"package": "himalaya",
|
|
34747
|
+
"type": "Text",
|
|
34748
|
+
"props": {
|
|
34749
|
+
"text": "$item.text",
|
|
34750
|
+
"style": "body",
|
|
34751
|
+
"maxLines": 50
|
|
34752
|
+
}
|
|
34753
|
+
}
|
|
34754
|
+
},
|
|
34755
|
+
{
|
|
34756
|
+
"id": "himi_chat_thread.image_$item.id",
|
|
34757
|
+
"body": {
|
|
34758
|
+
"package": "himalaya",
|
|
34759
|
+
"type": "Image",
|
|
34760
|
+
"props": {
|
|
34761
|
+
"url": "$item.imageUrl",
|
|
34762
|
+
"fillWidth": true,
|
|
34763
|
+
"aspectRatio": 1,
|
|
34764
|
+
"contentMode": "fill",
|
|
34765
|
+
"contentDescription": "$item.imageDescription"
|
|
34766
|
+
}
|
|
34767
|
+
},
|
|
34768
|
+
"modifiers": {
|
|
34769
|
+
"cornerRadius": 12,
|
|
34770
|
+
"fillMaxWidth": true,
|
|
34771
|
+
"hiddenPath": "$item.imageHidden"
|
|
34772
|
+
}
|
|
34773
|
+
},
|
|
34774
|
+
{
|
|
34775
|
+
"id": "",
|
|
34776
|
+
"body": {
|
|
34777
|
+
"package": "himalaya",
|
|
34778
|
+
"type": "Text",
|
|
34779
|
+
"props": {
|
|
34780
|
+
"text": "$item.when",
|
|
34781
|
+
"style": "caption",
|
|
34782
|
+
"color": "textSecondary",
|
|
34783
|
+
"collapseWhenEmpty": true
|
|
34784
|
+
}
|
|
34785
|
+
}
|
|
34786
|
+
}
|
|
34787
|
+
]
|
|
34788
|
+
}
|
|
34789
|
+
},
|
|
34790
|
+
"modifiers": {
|
|
34791
|
+
"background": {
|
|
34792
|
+
"token": "surfaceElevated"
|
|
34793
|
+
},
|
|
34794
|
+
"cornerRadius": 16,
|
|
34795
|
+
"maxWidth": 270,
|
|
34796
|
+
"padding": {
|
|
34797
|
+
"leading": 12,
|
|
34798
|
+
"trailing": 12,
|
|
34799
|
+
"top": 10,
|
|
34800
|
+
"bottom": 10
|
|
34670
34801
|
}
|
|
34671
34802
|
}
|
|
34803
|
+
},
|
|
34804
|
+
{
|
|
34805
|
+
"id": "",
|
|
34806
|
+
"body": {
|
|
34807
|
+
"package": "himalaya",
|
|
34808
|
+
"type": "Spacer",
|
|
34809
|
+
"props": {}
|
|
34810
|
+
}
|
|
34672
34811
|
}
|
|
34673
34812
|
]
|
|
34674
34813
|
}
|
|
34675
34814
|
},
|
|
34676
34815
|
"modifiers": {
|
|
34677
|
-
"
|
|
34678
|
-
|
|
34679
|
-
|
|
34680
|
-
|
|
34681
|
-
|
|
34682
|
-
|
|
34683
|
-
|
|
34684
|
-
|
|
34685
|
-
|
|
34686
|
-
"
|
|
34816
|
+
"hiddenPath": "$item.mine"
|
|
34817
|
+
}
|
|
34818
|
+
},
|
|
34819
|
+
{
|
|
34820
|
+
"id": "",
|
|
34821
|
+
"body": {
|
|
34822
|
+
"package": "himalaya",
|
|
34823
|
+
"type": "HStack",
|
|
34824
|
+
"props": {
|
|
34825
|
+
"spacing": 8,
|
|
34826
|
+
"alignment": "leading",
|
|
34827
|
+
"children": [
|
|
34828
|
+
{
|
|
34829
|
+
"id": "",
|
|
34830
|
+
"body": {
|
|
34831
|
+
"package": "himalaya",
|
|
34832
|
+
"type": "Spacer",
|
|
34833
|
+
"props": {}
|
|
34834
|
+
}
|
|
34835
|
+
},
|
|
34836
|
+
{
|
|
34837
|
+
"id": "",
|
|
34838
|
+
"body": {
|
|
34839
|
+
"package": "himalaya",
|
|
34840
|
+
"type": "VStack",
|
|
34841
|
+
"props": {
|
|
34842
|
+
"spacing": 6,
|
|
34843
|
+
"alignment": "leading",
|
|
34844
|
+
"children": [
|
|
34845
|
+
{
|
|
34846
|
+
"id": "",
|
|
34847
|
+
"body": {
|
|
34848
|
+
"package": "himalaya",
|
|
34849
|
+
"type": "Text",
|
|
34850
|
+
"props": {
|
|
34851
|
+
"text": "$item.text",
|
|
34852
|
+
"style": "body",
|
|
34853
|
+
"maxLines": 50
|
|
34854
|
+
}
|
|
34855
|
+
}
|
|
34856
|
+
},
|
|
34857
|
+
{
|
|
34858
|
+
"id": "himi_chat_thread.outgoing_image_$item.id",
|
|
34859
|
+
"body": {
|
|
34860
|
+
"package": "himalaya",
|
|
34861
|
+
"type": "Image",
|
|
34862
|
+
"props": {
|
|
34863
|
+
"url": "$item.imageUrl",
|
|
34864
|
+
"fillWidth": true,
|
|
34865
|
+
"aspectRatio": 1,
|
|
34866
|
+
"contentMode": "fill",
|
|
34867
|
+
"contentDescription": "$item.imageDescription"
|
|
34868
|
+
}
|
|
34869
|
+
},
|
|
34870
|
+
"modifiers": {
|
|
34871
|
+
"cornerRadius": 12,
|
|
34872
|
+
"fillMaxWidth": true,
|
|
34873
|
+
"hiddenPath": "$item.imageHidden"
|
|
34874
|
+
}
|
|
34875
|
+
},
|
|
34876
|
+
{
|
|
34877
|
+
"id": "",
|
|
34878
|
+
"body": {
|
|
34879
|
+
"package": "himalaya",
|
|
34880
|
+
"type": "Text",
|
|
34881
|
+
"props": {
|
|
34882
|
+
"text": "$item.when",
|
|
34883
|
+
"style": "caption",
|
|
34884
|
+
"color": "textSecondary",
|
|
34885
|
+
"collapseWhenEmpty": true
|
|
34886
|
+
}
|
|
34887
|
+
}
|
|
34888
|
+
}
|
|
34889
|
+
]
|
|
34890
|
+
}
|
|
34891
|
+
},
|
|
34892
|
+
"modifiers": {
|
|
34893
|
+
"background": {
|
|
34894
|
+
"token": "accentSurface"
|
|
34895
|
+
},
|
|
34896
|
+
"cornerRadius": 16,
|
|
34897
|
+
"maxWidth": 270,
|
|
34898
|
+
"padding": {
|
|
34899
|
+
"leading": 12,
|
|
34900
|
+
"trailing": 12,
|
|
34901
|
+
"top": 10,
|
|
34902
|
+
"bottom": 10
|
|
34903
|
+
}
|
|
34904
|
+
}
|
|
34905
|
+
}
|
|
34906
|
+
]
|
|
34687
34907
|
}
|
|
34908
|
+
},
|
|
34909
|
+
"modifiers": {
|
|
34910
|
+
"hiddenPath": "$item.notMine"
|
|
34688
34911
|
}
|
|
34689
34912
|
}
|
|
34690
34913
|
]
|
|
@@ -34705,15 +34928,14 @@ const SCREENS = runtimeSetting({
|
|
|
34705
34928
|
}
|
|
34706
34929
|
},
|
|
34707
34930
|
{
|
|
34708
|
-
"id": "",
|
|
34931
|
+
"id": "himi_chat_thread.empty",
|
|
34709
34932
|
"body": {
|
|
34710
34933
|
"package": "himalaya",
|
|
34711
|
-
"type": "
|
|
34934
|
+
"type": "EmptyState",
|
|
34712
34935
|
"props": {
|
|
34713
|
-
"
|
|
34714
|
-
"
|
|
34715
|
-
"
|
|
34716
|
-
"collapseWhenEmpty": true
|
|
34936
|
+
"icon": "chat",
|
|
34937
|
+
"title": "$service:spacesChatThread.emptyText",
|
|
34938
|
+
"subtitle": "Send a message to start the conversation."
|
|
34717
34939
|
}
|
|
34718
34940
|
},
|
|
34719
34941
|
"modifiers": {
|
|
@@ -34721,8 +34943,9 @@ const SCREENS = runtimeSetting({
|
|
|
34721
34943
|
"leading": 16,
|
|
34722
34944
|
"trailing": 16,
|
|
34723
34945
|
"top": 24,
|
|
34724
|
-
"bottom":
|
|
34725
|
-
}
|
|
34946
|
+
"bottom": 24
|
|
34947
|
+
},
|
|
34948
|
+
"hiddenPath": "$service:spacesChatThread.emptyHidden"
|
|
34726
34949
|
}
|
|
34727
34950
|
},
|
|
34728
34951
|
{
|
|
@@ -34864,29 +35087,37 @@ const SCREENS = runtimeSetting({
|
|
|
34864
35087
|
"bottom": 0
|
|
34865
35088
|
}
|
|
34866
35089
|
}
|
|
34867
|
-
}
|
|
34868
|
-
|
|
34869
|
-
|
|
34870
|
-
|
|
34871
|
-
|
|
34872
|
-
|
|
34873
|
-
|
|
34874
|
-
|
|
34875
|
-
|
|
34876
|
-
|
|
34877
|
-
|
|
34878
|
-
|
|
34879
|
-
|
|
34880
|
-
|
|
34881
|
-
|
|
34882
|
-
|
|
34883
|
-
|
|
34884
|
-
|
|
34885
|
-
|
|
34886
|
-
|
|
34887
|
-
|
|
34888
|
-
|
|
34889
|
-
|
|
35090
|
+
}
|
|
35091
|
+
]
|
|
35092
|
+
}
|
|
35093
|
+
},
|
|
35094
|
+
"modifiers": {
|
|
35095
|
+
"background": {
|
|
35096
|
+
"token": "surfaceSecondary"
|
|
35097
|
+
},
|
|
35098
|
+
"onAppear": {
|
|
35099
|
+
"actionId": "tier5.spacesChatThread.load"
|
|
35100
|
+
}
|
|
35101
|
+
}
|
|
35102
|
+
},
|
|
35103
|
+
"logicBundle": {
|
|
35104
|
+
"url": "http://localhost:8190/v1/bundles/wix-spaces-chat-thread?integrity=",
|
|
35105
|
+
"integrity": "",
|
|
35106
|
+
"contentType": "application/javascript+ts-bundle",
|
|
35107
|
+
"declaredCapabilities": [
|
|
35108
|
+
"wix_api"
|
|
35109
|
+
],
|
|
35110
|
+
"minBinaryVersion": "0.1.0",
|
|
35111
|
+
"bundleVersion": "0.0.1"
|
|
35112
|
+
},
|
|
35113
|
+
"stickyFooter": {
|
|
35114
|
+
"id": "himi_chat_thread.composer_footer",
|
|
35115
|
+
"body": {
|
|
35116
|
+
"package": "himalaya",
|
|
35117
|
+
"type": "VStack",
|
|
35118
|
+
"props": {
|
|
35119
|
+
"spacing": 4,
|
|
35120
|
+
"children": [
|
|
34890
35121
|
{
|
|
34891
35122
|
"id": "",
|
|
34892
35123
|
"body": {
|
|
@@ -34901,15 +35132,31 @@ const SCREENS = runtimeSetting({
|
|
|
34901
35132
|
"package": "himalaya",
|
|
34902
35133
|
"type": "TextField",
|
|
34903
35134
|
"props": {
|
|
34904
|
-
"placeholder": "
|
|
35135
|
+
"placeholder": "Type your message…",
|
|
34905
35136
|
"value": "$service:spacesChatThread.draft",
|
|
34906
35137
|
"onChange": {
|
|
34907
35138
|
"actionId": "tier5.spacesChatThread.setDraft"
|
|
34908
|
-
}
|
|
35139
|
+
},
|
|
35140
|
+
"onSubmit": {
|
|
35141
|
+
"actionId": "tier5.spacesChatThread.send"
|
|
35142
|
+
},
|
|
35143
|
+
"submitLabel": "send",
|
|
35144
|
+
"borderless": true
|
|
34909
35145
|
}
|
|
34910
35146
|
},
|
|
34911
35147
|
"modifiers": {
|
|
34912
|
-
"weight": 1
|
|
35148
|
+
"weight": 1,
|
|
35149
|
+
"accessibilityLabel": "Type your message",
|
|
35150
|
+
"padding": {
|
|
35151
|
+
"leading": 16,
|
|
35152
|
+
"trailing": 8,
|
|
35153
|
+
"top": 8,
|
|
35154
|
+
"bottom": 8
|
|
35155
|
+
},
|
|
35156
|
+
"background": {
|
|
35157
|
+
"token": "surfaceSecondary"
|
|
35158
|
+
},
|
|
35159
|
+
"cornerRadius": 28
|
|
34913
35160
|
}
|
|
34914
35161
|
},
|
|
34915
35162
|
{
|
|
@@ -34919,11 +35166,21 @@ const SCREENS = runtimeSetting({
|
|
|
34919
35166
|
"type": "Button",
|
|
34920
35167
|
"props": {
|
|
34921
35168
|
"title": "Send",
|
|
34922
|
-
"style": "primary",
|
|
34923
35169
|
"action": {
|
|
34924
35170
|
"actionId": "tier5.spacesChatThread.send"
|
|
34925
|
-
}
|
|
35171
|
+
},
|
|
35172
|
+
"style": "primary",
|
|
35173
|
+
"iconOnly": true,
|
|
35174
|
+
"leadingIcon": "send",
|
|
35175
|
+
"cornerRadius": 22,
|
|
35176
|
+
"minHeight": 44
|
|
34926
35177
|
}
|
|
35178
|
+
},
|
|
35179
|
+
"modifiers": {
|
|
35180
|
+
"width": 44,
|
|
35181
|
+
"height": 44,
|
|
35182
|
+
"accessibilityLabel": "Send",
|
|
35183
|
+
"accessibilityRole": "button"
|
|
34927
35184
|
}
|
|
34928
35185
|
}
|
|
34929
35186
|
]
|
|
@@ -34934,9 +35191,29 @@ const SCREENS = runtimeSetting({
|
|
|
34934
35191
|
"leading": 12,
|
|
34935
35192
|
"trailing": 12,
|
|
34936
35193
|
"top": 8,
|
|
34937
|
-
"bottom":
|
|
34938
|
-
}
|
|
34939
|
-
|
|
35194
|
+
"bottom": 8
|
|
35195
|
+
}
|
|
35196
|
+
}
|
|
35197
|
+
},
|
|
35198
|
+
{
|
|
35199
|
+
"id": "",
|
|
35200
|
+
"body": {
|
|
35201
|
+
"package": "himalaya",
|
|
35202
|
+
"type": "Text",
|
|
35203
|
+
"props": {
|
|
35204
|
+
"text": "$service:spacesChatThread.sendHint",
|
|
35205
|
+
"style": "footnote",
|
|
35206
|
+
"color": "accentPrimary",
|
|
35207
|
+
"collapseWhenEmpty": true
|
|
35208
|
+
}
|
|
35209
|
+
},
|
|
35210
|
+
"modifiers": {
|
|
35211
|
+
"padding": {
|
|
35212
|
+
"leading": 16,
|
|
35213
|
+
"trailing": 16,
|
|
35214
|
+
"top": 0,
|
|
35215
|
+
"bottom": 4
|
|
35216
|
+
}
|
|
34940
35217
|
}
|
|
34941
35218
|
}
|
|
34942
35219
|
]
|
|
@@ -34946,20 +35223,12 @@ const SCREENS = runtimeSetting({
|
|
|
34946
35223
|
"background": {
|
|
34947
35224
|
"token": "surfacePrimary"
|
|
34948
35225
|
},
|
|
34949
|
-
"
|
|
34950
|
-
"actionId": "tier5.spacesChatThread.load"
|
|
34951
|
-
}
|
|
35226
|
+
"hiddenPath": "$service:spacesChatThread.noEntity"
|
|
34952
35227
|
}
|
|
34953
35228
|
},
|
|
34954
|
-
"
|
|
34955
|
-
"
|
|
34956
|
-
"
|
|
34957
|
-
"contentType": "application/javascript+ts-bundle",
|
|
34958
|
-
"declaredCapabilities": [
|
|
34959
|
-
"wix_api"
|
|
34960
|
-
],
|
|
34961
|
-
"minBinaryVersion": "0.1.0",
|
|
34962
|
-
"bundleVersion": "0.0.1"
|
|
35229
|
+
"transcriptScroll": {
|
|
35230
|
+
"sendPath": "$service:spacesChatThread.scrollSend",
|
|
35231
|
+
"jumpLabel": "Jump to latest"
|
|
34963
35232
|
}
|
|
34964
35233
|
},
|
|
34965
35234
|
"himi_new_message": {
|
|
@@ -12,19 +12,28 @@
|
|
|
12
12
|
import { defineService, type PollDirective, type StreamDirective } from "@himalaya/state";
|
|
13
13
|
import { getMessages, sendChatMessage, getOrCreateDirectChatroom, getChatToken, type ChatMessage } from "@himalaya/wix-chat-web";
|
|
14
14
|
import { getMyData } from "@himalaya/wix-users";
|
|
15
|
-
import { formatMonthDayTime } from "../_shared/engine-intl.js";
|
|
15
|
+
import { formatMonthDay, formatMonthDayTime } from "../_shared/engine-intl.js";
|
|
16
|
+
import { initials } from "../_shared/himi-text.js";
|
|
17
|
+
import { copyParam, stashCopy } from "../_shared/himi-copy.js";
|
|
16
18
|
import { presence, presenceAfterError, detailInputId, type PresenceFlags } from "../_shared/detail-presence.js";
|
|
17
19
|
import { writeMayHaveLanded } from "../_shared/owner-load-error.js";
|
|
18
20
|
import { chatRealtimeConfig, createReloadGate, createSendIntents, createVisitToken, liveDirectives, postWithVisitToken, liveFallback, type ChatLiveMode, type ChatRealtimeConfig } from "../_shared/chat-live.js";
|
|
19
21
|
|
|
20
22
|
const SERVICE = "spacesChatThread";
|
|
23
|
+
const EMPTY_TEXT = "No messages yet. Say hello!";
|
|
21
24
|
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// (
|
|
25
|
-
interface Row {
|
|
25
|
+
// `mine` / `notMine` pick the incoming or outgoing row layout (the descriptor hides the other
|
|
26
|
+
// one). Empty strings and `*Hidden` flags let a row drop its badge, avatar or attachment.
|
|
27
|
+
// `leadFill` (" " for my messages) stays for screens that right-align with a gated spacer.
|
|
28
|
+
interface Row {
|
|
29
|
+
id: string; text: string; when: string; leadFill: string; mine: boolean; notMine: boolean;
|
|
30
|
+
senderBadge: string; avatarUrl: string; avatarInitials: string;
|
|
31
|
+
imageUrl: string; imageDescription: string; imageHidden: boolean; dayLabel: string; dayHidden: boolean;
|
|
32
|
+
}
|
|
26
33
|
interface S extends PresenceFlags {
|
|
27
|
-
status: "loading" | "ready"; messages: Row[]; emptyText: string; draft: string; sendHint: string; resolvedChatroomId: string; myUserId: string;
|
|
34
|
+
status: "loading" | "ready"; messages: Row[]; emptyText: string; emptyHidden: boolean; draft: string; sendHint: string; resolvedChatroomId: string; myUserId: string;
|
|
35
|
+
/** Send token for the host's transcript scroll: a new value follows the tail. */
|
|
36
|
+
scrollSend: string;
|
|
28
37
|
/** Which path keeps the open thread live (diagnostic). */
|
|
29
38
|
live?: ChatLiveMode;
|
|
30
39
|
_stream?: StreamDirective;
|
|
@@ -55,14 +64,61 @@ async function resolveChatroomId(raw: string, kind: string): Promise<string> {
|
|
|
55
64
|
function val(arg?: unknown): string {
|
|
56
65
|
return typeof arg === "string" ? arg : ((arg as { value?: string; text?: string })?.value ?? (arg as { text?: string })?.text ?? "");
|
|
57
66
|
}
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
function dayKeyOf(d: Date): string {
|
|
68
|
+
return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
|
|
69
|
+
}
|
|
70
|
+
/** chat-web marks the business side as "business", "businessUser" or "user" (the same predicate
|
|
71
|
+
* as wix-owner-inbox-thread); a visitor or member is anything else. */
|
|
72
|
+
function isBusinessSender(m: ChatMessage): boolean {
|
|
73
|
+
const type = String(m.sender?.type ?? "").toLowerCase();
|
|
74
|
+
return type === "business" || type === "businessuser" || type === "user";
|
|
75
|
+
}
|
|
76
|
+
function norm(m: ChatMessage, i: number, myUserId: string): Row & { timestamp: number; dayKey: string } {
|
|
77
|
+
const mm = m as unknown as {
|
|
78
|
+
id?: string; summary?: string; data?: Array<{ mimeType?: string; data?: { text?: string; uri?: string; thumbnailUrl?: string } }>;
|
|
79
|
+
payload?: { text?: string }; content?: string; createdAt?: number; createdDate?: string;
|
|
80
|
+
senderDisplayData?: { name?: string; image?: { url?: string } };
|
|
81
|
+
};
|
|
82
|
+
const text = mm.data?.find((part) => part.data?.text)?.data?.text || mm.summary || mm.payload?.text || mm.content || "";
|
|
61
83
|
const ms = mm.createdAt || (mm.createdDate ? Date.parse(mm.createdDate) : 0);
|
|
62
|
-
const
|
|
84
|
+
const date = ms ? new Date(ms) : null;
|
|
85
|
+
const senderName = mm.senderDisplayData?.name || "";
|
|
86
|
+
const avatarUrl = mm.senderDisplayData?.image?.url || "";
|
|
87
|
+
const image = mm.data?.find((part) => part.mimeType?.startsWith("image/") || part.data?.thumbnailUrl);
|
|
88
|
+
const imageUrl = image?.data?.thumbnailUrl || image?.data?.uri || "";
|
|
63
89
|
// Mine when the message sender matches my user id (resolved once per load).
|
|
64
90
|
const mine = !!(myUserId && m.sender?.userId && m.sender.userId === myUserId);
|
|
65
|
-
return {
|
|
91
|
+
return {
|
|
92
|
+
id: mm.id || `m${i}`, text: text || "Message", when: date ? formatMonthDayTime(date) : "", leadFill: mine ? " " : "", mine, notMine: !mine,
|
|
93
|
+
senderBadge: isBusinessSender(m) ? copyParam("chatThread.businessBadge", "Business") : "",
|
|
94
|
+
avatarUrl, avatarInitials: initials(senderName),
|
|
95
|
+
imageUrl, imageDescription: imageUrl ? `${senderName || "Message"} attachment` : "", imageHidden: !imageUrl,
|
|
96
|
+
dayLabel: "", dayHidden: true, timestamp: ms, dayKey: date ? dayKeyOf(date) : "",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Oldest first, one row per message id, and a day label on the first message of each day. */
|
|
101
|
+
function rowsOf(messages: ChatMessage[], myUserId: string): Row[] {
|
|
102
|
+
const sorted = messages.map((m, i) => norm(m, i, myUserId)).sort((a, b) => a.timestamp - b.timestamp);
|
|
103
|
+
const today = new Date();
|
|
104
|
+
const todayKey = dayKeyOf(today);
|
|
105
|
+
const yesterdayKey = dayKeyOf(new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1));
|
|
106
|
+
const seen = new Set<string>();
|
|
107
|
+
const rows: Row[] = [];
|
|
108
|
+
let previousDay = "";
|
|
109
|
+
for (const { timestamp, dayKey, ...row } of sorted) {
|
|
110
|
+
if (seen.has(row.id)) continue;
|
|
111
|
+
seen.add(row.id);
|
|
112
|
+
const startsDay = !!dayKey && dayKey !== previousDay;
|
|
113
|
+
if (dayKey) previousDay = dayKey;
|
|
114
|
+
const dayLabel = !startsDay ? "" : dayKey === todayKey ? copyParam("chatThread.today", "Today") : dayKey === yesterdayKey ? copyParam("chatThread.yesterday", "Yesterday") : formatMonthDay(new Date(timestamp));
|
|
115
|
+
rows.push({ ...row, dayLabel, dayHidden: !dayLabel });
|
|
116
|
+
}
|
|
117
|
+
return rows;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function nextScrollToken(prev: string): string {
|
|
121
|
+
return String((Number(prev) || 0) + 1);
|
|
66
122
|
}
|
|
67
123
|
|
|
68
124
|
// ONE member chatToken per visit (`_shared/chat-live.ts`): a 4 s re-read would otherwise
|
|
@@ -74,7 +130,7 @@ async function reload(id: string, myUserId: string): Promise<Row[]> {
|
|
|
74
130
|
const token = memberToken.get();
|
|
75
131
|
try {
|
|
76
132
|
const msgs = await getMessages(id, 30, { role: "member", chatToken: await token });
|
|
77
|
-
return (msgs ?? []
|
|
133
|
+
return rowsOf(msgs ?? [], myUserId);
|
|
78
134
|
} catch (e) {
|
|
79
135
|
memberToken.drop(token);
|
|
80
136
|
throw e;
|
|
@@ -114,7 +170,7 @@ async function reread(s: S): Promise<S> {
|
|
|
114
170
|
const messages = await gate.request();
|
|
115
171
|
const now = svc.getState();
|
|
116
172
|
if (now.resolvedChatroomId !== s.resolvedChatroomId) return now;
|
|
117
|
-
return { ...now, messages, emptyText: messages.length ? "" : now.emptyText };
|
|
173
|
+
return { ...now, messages, emptyText: messages.length ? "" : now.emptyText || EMPTY_TEXT, emptyHidden: messages.length > 0 };
|
|
118
174
|
} catch {
|
|
119
175
|
return svc.getState();
|
|
120
176
|
}
|
|
@@ -139,31 +195,32 @@ const svc = defineService<S, {
|
|
|
139
195
|
onStreamEvent: (s: S) => Promise<S>;
|
|
140
196
|
onStreamError: (s: S) => S;
|
|
141
197
|
}>(SERVICE, {
|
|
142
|
-
state: { status: "loading", messages: [], emptyText: "", draft: "", sendHint: "", resolvedChatroomId: "", myUserId: "", ...presence("loading") },
|
|
198
|
+
state: { status: "loading", messages: [], emptyText: "", emptyHidden: true, scrollSend: "", draft: "", sendHint: "", resolvedChatroomId: "", myUserId: "", ...presence("loading") },
|
|
143
199
|
actions: {
|
|
144
200
|
load: async (s, a) => {
|
|
201
|
+
stashCopy(a);
|
|
145
202
|
const param = (a as { chatRealtime?: unknown } | null | undefined)?.chatRealtime;
|
|
146
203
|
if (param !== undefined) realtime = chatRealtimeConfig(param);
|
|
147
204
|
const raw = inputId();
|
|
148
205
|
// No thread / recipient id (a stale link, a route opened directly) -> the shared
|
|
149
206
|
// not-found state, and no composer: there is nobody to send to.
|
|
150
|
-
if (!detailInputId()) return { ...s, status: "ready", messages: [], emptyText: "", ...presence("missing") };
|
|
207
|
+
if (!detailInputId()) return { ...s, status: "ready", messages: [], emptyText: "", emptyHidden: true, ...presence("missing") };
|
|
151
208
|
memberToken.reset();
|
|
152
209
|
// Resolve chatroom + my user id concurrently (myUserId tags own messages).
|
|
153
210
|
const [chatroomId, myUserId] = await Promise.all([resolveChatroomId(raw, inputKind()), resolveMyUserId()]);
|
|
154
211
|
// A recipient whose 1:1 chatroom could not be resolved is not a conversation yet:
|
|
155
212
|
// reload("") would return [] without throwing and publish a live, empty thread
|
|
156
213
|
// whose Send can only fail. Show the load-error line instead.
|
|
157
|
-
if (!chatroomId) return { ...s, status: "ready", resolvedChatroomId: "", myUserId, messages: [], emptyText: "", ...presence("failed") };
|
|
214
|
+
if (!chatroomId) return { ...s, status: "ready", resolvedChatroomId: "", myUserId, messages: [], emptyText: "", emptyHidden: true, ...presence("failed") };
|
|
158
215
|
try {
|
|
159
216
|
const messages = await reload(chatroomId, myUserId);
|
|
160
217
|
current = { chatroomId, myUserId };
|
|
161
|
-
return { ...s, status: "ready", resolvedChatroomId: chatroomId, myUserId, messages, emptyText: messages.length ? "" :
|
|
218
|
+
return { ...s, status: "ready", resolvedChatroomId: chatroomId, myUserId, messages, emptyText: messages.length ? "" : EMPTY_TEXT, emptyHidden: messages.length > 0, scrollSend: messages.length ? nextScrollToken(s.scrollSend) : "", ...presence("found"), ...liveDirectives(realtime, chatroomId, SERVICE) };
|
|
162
219
|
} catch (e) {
|
|
163
220
|
// A thread whose messages cannot be read is not a live, empty conversation: a 404
|
|
164
221
|
// is the shared not-found state, anything else the load-error line — never a
|
|
165
222
|
// "Say hello!" composer that can only fail.
|
|
166
|
-
return { ...s, status: "ready", resolvedChatroomId: chatroomId, myUserId, messages: [], emptyText: "", ...presenceAfterError(e) };
|
|
223
|
+
return { ...s, status: "ready", resolvedChatroomId: chatroomId, myUserId, messages: [], emptyText: "", emptyHidden: true, ...presenceAfterError(e) };
|
|
167
224
|
}
|
|
168
225
|
},
|
|
169
226
|
setDraft: (s, a) => ({ ...s, draft: val(a) }),
|
|
@@ -208,11 +265,11 @@ const svc = defineService<S, {
|
|
|
208
265
|
try {
|
|
209
266
|
const messages = await gate.request();
|
|
210
267
|
const now = liveState();
|
|
211
|
-
return { ...now, resolvedChatroomId: chatroomId, myUserId, messages, draft: cleared(now), emptyText: "", sendHint: "Sent ✓" };
|
|
268
|
+
return { ...now, resolvedChatroomId: chatroomId, myUserId, messages, draft: cleared(now), emptyText: "", emptyHidden: true, scrollSend: nextScrollToken(now.scrollSend), sendHint: "Sent ✓" };
|
|
212
269
|
} catch {
|
|
213
270
|
const now = liveState();
|
|
214
|
-
const
|
|
215
|
-
return { ...now, resolvedChatroomId: chatroomId, myUserId, messages: [...now.messages,
|
|
271
|
+
const local: Row = { id: `local-${now.messages.length}`, text, when: "", leadFill: " ", mine: true, notMine: false, senderBadge: "", avatarUrl: "", avatarInitials: "", imageUrl: "", imageDescription: "", imageHidden: true, dayLabel: "", dayHidden: true };
|
|
272
|
+
return { ...now, resolvedChatroomId: chatroomId, myUserId, messages: [...now.messages, local], draft: cleared(now), emptyText: "", emptyHidden: true, scrollSend: nextScrollToken(now.scrollSend), sendHint: "Sent ✓" };
|
|
216
273
|
}
|
|
217
274
|
} finally {
|
|
218
275
|
sendingNow = false;
|