@dereekb/dbx-components-mcp 14.14.0 → 14.16.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.
@@ -10,7 +10,7 @@ var __export = (target, all) => {
10
10
  // packages/dbx-components-mcp/package.json
11
11
  var package_default = {
12
12
  name: "@dereekb/dbx-components-mcp",
13
- version: "14.14.0",
13
+ version: "14.16.0",
14
14
  sideEffects: false,
15
15
  type: "module",
16
16
  bin: {
@@ -22,8 +22,8 @@ var package_default = {
22
22
  "ts-morph": "^21.0.0"
23
23
  },
24
24
  peerDependencies: {
25
- "@dereekb/dbx-cli": "14.14.0",
26
- "@dereekb/util": "14.14.0"
25
+ "@dereekb/dbx-cli": "14.16.0",
26
+ "@dereekb/util": "14.16.0"
27
27
  }
28
28
  };
29
29
 
@@ -2496,10 +2496,18 @@ var FIREBASE_MODELS = [
2496
2496
  tsType: "Maybe<NotificationHealthCheck>",
2497
2497
  optional: true,
2498
2498
  description: "The result of the most recent notification delivery health check run for this user."
2499
+ },
2500
+ {
2501
+ name: "tso",
2502
+ longName: "textStoppedPhoneNumbers",
2503
+ converter: "optionalFirestoreArray<E164PhoneNumber>({ filterUnique: true, dontStoreIfEmpty: true })",
2504
+ tsType: "Maybe<E164PhoneNumber[]>",
2505
+ optional: true,
2506
+ description: "Phone numbers that replied STOP to a text. Texts to a number in this list resolve to off, at send time and in the health check."
2499
2507
  }
2500
2508
  ],
2501
2509
  enums: [],
2502
- detectionHints: ["b", "x", "dc", "gc", "bc", "ns", "hc"],
2510
+ detectionHints: ["b", "x", "dc", "gc", "bc", "ns", "hc", "tso"],
2503
2511
  description: "A global notification user profile that tracks notification preferences and box subscriptions.",
2504
2512
  modelGroup: "Notification",
2505
2513
  collectionKind: "root",
@@ -6492,15 +6500,16 @@ function parseSignalInput(property) {
6492
6500
  ...defaultValue === void 0 ? {} : { defaultValue }
6493
6501
  };
6494
6502
  }
6503
+ var SIGNAL_INPUT_FUNCTION_NAMES = /* @__PURE__ */ new Set(["input", "model"]);
6495
6504
  function classifySignalInputCall(call) {
6496
6505
  const expression = call.getExpression();
6497
6506
  let result;
6498
- if (Node9.isIdentifier(expression) && expression.getText() === "input") {
6507
+ if (Node9.isIdentifier(expression) && SIGNAL_INPUT_FUNCTION_NAMES.has(expression.getText())) {
6499
6508
  result = "plain";
6500
6509
  } else if (Node9.isPropertyAccessExpression(expression)) {
6501
6510
  const baseExpr = expression.getExpression();
6502
6511
  const propertyName = expression.getName();
6503
- if (Node9.isIdentifier(baseExpr) && baseExpr.getText() === "input" && propertyName === "required") {
6512
+ if (Node9.isIdentifier(baseExpr) && SIGNAL_INPUT_FUNCTION_NAMES.has(baseExpr.getText()) && propertyName === "required") {
6504
6513
  result = "required";
6505
6514
  }
6506
6515
  }
@@ -24747,9 +24756,9 @@ var RULE_CATALOG = [
24747
24756
  {
24748
24757
  code: "NOTIF_TASK_IN_VALIDATE_WITHOUT_HANDLER",
24749
24758
  source: "dbx_notification_m_validate_app",
24750
- severity: "warning",
24759
+ severity: "error",
24751
24760
  title: "A task type appears in `validate:` but has no matching `handlers:` registration",
24752
- whatItFlags: "A task type appears in `validate:` but has no matching `handlers:` registration.",
24761
+ whatItFlags: "A task type appears in `validate:` but has no matching `handlers:` registration.\n`notificationTaskService()` throws at startup for this.",
24753
24762
  whenItApplies: "When `notificationTaskService({ validate, handlers })` lists a task type in `validate:` that the `handlers:` traversal doesn't reach.",
24754
24763
  whenItDoesNotApply: "Tasks whose handler is provided by an upstream module (covered by the trust list).",
24755
24764
  canonicalFix: "Add the matching `NotificationTaskServiceTaskHandlerConfig` for the task and include it in `handlers:`."
@@ -24870,6 +24879,54 @@ var RULE_CATALOG = [
24870
24879
  whenItDoesNotApply: "Spreads from trust-listed `@dereekb/*` modules.",
24871
24880
  canonicalFix: "Declare the missing function or import it from a trusted module."
24872
24881
  },
24882
+ {
24883
+ code: "NOTIF_TEMPLATE_FACTORY_UNLISTED_DELIVERY_METHOD",
24884
+ source: "dbx_notification_m_validate_app",
24885
+ severity: "warning",
24886
+ title: "A template's message factory returns channel content for a delivery method its `NotificationTemplateTypeInfo` does not list in `userConfigurableDeliveryMethods` or `forcedDeliveryMethods`",
24887
+ whatItFlags: "A template's message factory returns channel content for a delivery method its `NotificationTemplateTypeInfo` does not list in `userConfigurableDeliveryMethods` or `forcedDeliveryMethods`.",
24888
+ whenItApplies: "Template types whose info declares an explicit `userConfigurableDeliveryMethods` array, or only a `forcedDeliveryMethods` array (forcing a method changes the `userConfigurableDeliveryMethods` default to none), and whose handler factory (or the function its `factory:` names) builds an object literal with `emailContent`, `textContent` or `notificationSummaryContent` for a method neither array lists.",
24889
+ whenItDoesNotApply: "Infos with neither `userConfigurableDeliveryMethods` nor `forcedDeliveryMethods` (the defaults list email, text and notification summary), lists the scanner cannot read statically (including a `forcedDeliveryMethods` it cannot read), and content built outside the factory's own source \u2014 the send pipeline's runtime warning (`notificationMessageFunctionWithUnlistedDeliveryMethodsWarning`) still covers those.",
24890
+ canonicalFix: "Stop returning the unlisted content from the factory, or add the delivery method to the info's `userConfigurableDeliveryMethods` (or `forcedDeliveryMethods`).",
24891
+ seeAlso: [
24892
+ {
24893
+ kind: "artifact",
24894
+ target: "notification-template"
24895
+ }
24896
+ ]
24897
+ },
24898
+ {
24899
+ code: "NOTIF_TEMPLATE_FORCED_DELIVERY_METHOD_EXPLICIT_OPT_IN",
24900
+ source: "dbx_notification_m_validate_app",
24901
+ severity: "error",
24902
+ title: "A `NotificationTemplateTypeInfo` forces a delivery method while `onlySendToExplicitlyEnabledRecipients` is `true`. A forced method is on by default, which an opt-in only type cannot be, and the app throws at startup when the info record is built",
24903
+ whatItFlags: "A `NotificationTemplateTypeInfo` forces a delivery method while `onlySendToExplicitlyEnabledRecipients` is `true`. A forced method is on by default, which an opt-in only type cannot be, and the app throws at startup when the info record is built.",
24904
+ whenItApplies: "Infos with a non-empty statically read `forcedDeliveryMethods` array and `onlySendToExplicitlyEnabledRecipients: true`.",
24905
+ whenItDoesNotApply: "Infos without `forcedDeliveryMethods`, lists the scanner cannot read statically, and `onlySendToExplicitlyEnabledRecipients` values other than a `true` literal.",
24906
+ canonicalFix: "Remove `forcedDeliveryMethods`, or remove `onlySendToExplicitlyEnabledRecipients` from the info.",
24907
+ seeAlso: [
24908
+ {
24909
+ kind: "artifact",
24910
+ target: "notification-template"
24911
+ }
24912
+ ]
24913
+ },
24914
+ {
24915
+ code: "NOTIF_TEMPLATE_FORCED_TEXT_DELIVERY_METHOD",
24916
+ source: "dbx_notification_m_validate_app",
24917
+ severity: "error",
24918
+ title: "A `NotificationTemplateTypeInfo` lists the text delivery method in `forcedDeliveryMethods`. Texts cannot be forced, and the app throws at startup when the info record is built",
24919
+ whatItFlags: "A `NotificationTemplateTypeInfo` lists the text delivery method in `forcedDeliveryMethods`. Texts cannot be forced, and the app throws at startup when the info record is built.",
24920
+ whenItApplies: "Infos whose statically read `forcedDeliveryMethods` array contains `NotificationDeliveryMethod.TEXT`.",
24921
+ whenItDoesNotApply: "Infos without `forcedDeliveryMethods`, and lists the scanner cannot read statically.",
24922
+ canonicalFix: "Remove `NotificationDeliveryMethod.TEXT` from `forcedDeliveryMethods`. Texts stay opt-in for the recipient.",
24923
+ seeAlso: [
24924
+ {
24925
+ kind: "artifact",
24926
+ target: "notification-template"
24927
+ }
24928
+ ]
24929
+ },
24873
24930
  {
24874
24931
  code: "NOTIF_TEMPLATE_INFO_MISSING",
24875
24932
  source: "dbx_notification_m_validate_app",
@@ -30840,6 +30897,16 @@ function readStringProperty2(obj, name) {
30840
30897
  }
30841
30898
  return void 0;
30842
30899
  }
30900
+ function readBooleanProperty2(obj, name) {
30901
+ const init = unwrapAsExpressions(getPropertyInitializer(obj, name));
30902
+ let result;
30903
+ if (init?.getKind() === SyntaxKind11.TrueKeyword) {
30904
+ result = true;
30905
+ } else if (init?.getKind() === SyntaxKind11.FalseKeyword) {
30906
+ result = false;
30907
+ }
30908
+ return result;
30909
+ }
30843
30910
  function readIdentifierProperty(obj, name) {
30844
30911
  const init = unwrapAsExpressions(getPropertyInitializer(obj, name));
30845
30912
  if (init && Node26.isIdentifier(init)) {
@@ -31566,7 +31633,8 @@ __export(notification_m_validate_app_exports, {
31566
31633
  extractAppNotifications: () => extractAppNotifications,
31567
31634
  formatResult: () => formatResult6,
31568
31635
  inspectAppNotifications: () => inspectAppNotifications,
31569
- validateAppNotifications: () => validateAppNotifications
31636
+ validateAppNotifications: () => validateAppNotifications,
31637
+ validateExtractedAppNotifications: () => validateExtractedAppNotifications
31570
31638
  });
31571
31639
 
31572
31640
  // packages/dbx-cli/validate/src/lib/notification-m-validate-app/extract.ts
@@ -31587,6 +31655,27 @@ var TEMPLATE_CONFIGS_ARRAY_FACTORY_SUFFIX = "NotificationTemplateServiceConfigsA
31587
31655
  var TEMPLATE_CONFIGS_ARRAY_TOKEN = "NOTIFICATION_TEMPLATE_SERVICE_CONFIGS_ARRAY_TOKEN";
31588
31656
  var CHECKPOINT_ALIAS_SUFFIX = "NotificationTaskCheckpoint";
31589
31657
  var TASK_DATA_INTERFACE_SUFFIX = "NotificationTaskData";
31658
+ var USER_CONFIGURABLE_DELIVERY_METHODS_PROPERTY = "userConfigurableDeliveryMethods";
31659
+ var FORCED_DELIVERY_METHODS_PROPERTY = "forcedDeliveryMethods";
31660
+ var ONLY_SEND_TO_EXPLICITLY_ENABLED_RECIPIENTS_PROPERTY = "onlySendToExplicitlyEnabledRecipients";
31661
+ var DELIVERY_METHOD_ENUM = "NotificationDeliveryMethod";
31662
+ var DELIVERY_METHOD_BY_MEMBER = /* @__PURE__ */ new Map([
31663
+ ["EMAIL", "EMAIL"],
31664
+ ["TEXT", "TEXT"],
31665
+ ["PUSH", "PUSH"],
31666
+ ["NOTIFICATION_SUMMARY", "NOTIFICATION_SUMMARY"]
31667
+ ]);
31668
+ var DELIVERY_METHOD_BY_CODE = /* @__PURE__ */ new Map([
31669
+ ["e", "EMAIL"],
31670
+ ["t", "TEXT"],
31671
+ ["p", "PUSH"],
31672
+ ["n", "NOTIFICATION_SUMMARY"]
31673
+ ]);
31674
+ var CONTENT_PROPERTY_DELIVERY_METHODS = /* @__PURE__ */ new Map([
31675
+ ["emailContent", "EMAIL"],
31676
+ ["textContent", "TEXT"],
31677
+ ["notificationSummaryContent", "NOTIFICATION_SUMMARY"]
31678
+ ]);
31590
31679
  function extractAppNotifications(inspection) {
31591
31680
  const { componentSources, apiSources } = buildInMemoryProject(inspection);
31592
31681
  const trustedExternalIdentifiers = collectTrustedExternalIdentifiers([...componentSources, ...apiSources]);
@@ -31660,13 +31749,20 @@ function extractTemplateTypeInfos(sources) {
31660
31749
  if (!nameMatches && !typeMatches) continue;
31661
31750
  const obj = asObjectLiteral(decl.getInitializer());
31662
31751
  if (!obj) continue;
31752
+ const deliveryMethods = readDeliveryMethodsProperty(obj, sf, USER_CONFIGURABLE_DELIVERY_METHODS_PROPERTY);
31753
+ const forcedDeliveryMethods = readDeliveryMethodsProperty(obj, sf, FORCED_DELIVERY_METHODS_PROPERTY);
31663
31754
  const entry = {
31664
31755
  symbolName: name,
31665
31756
  typeConstantName: readIdentifierProperty(obj, "type"),
31666
31757
  humanName: readStringProperty2(obj, "name"),
31667
31758
  description: readStringProperty2(obj, "description"),
31668
- notificationMIdentity: readIdentifierProperty(obj, "notificationMIdentity"),
31759
+ notificationModelIdentity: readIdentifierProperty(obj, "notificationModelIdentity"),
31669
31760
  targetModelIdentity: readIdentifierProperty(obj, "targetModelIdentity"),
31761
+ userConfigurableDeliveryMethods: deliveryMethods.methods,
31762
+ userConfigurableDeliveryMethodsSource: deliveryMethods.source,
31763
+ forcedDeliveryMethods: forcedDeliveryMethods.methods,
31764
+ forcedDeliveryMethodsSource: forcedDeliveryMethods.source,
31765
+ onlySendToExplicitlyEnabledRecipients: readBooleanProperty2(obj, ONLY_SEND_TO_EXPLICITLY_ENABLED_RECIPIENTS_PROPERTY),
31670
31766
  sourceFile: rel,
31671
31767
  line: decl.getStartLineNumber()
31672
31768
  };
@@ -31676,6 +31772,44 @@ function extractTemplateTypeInfos(sources) {
31676
31772
  }
31677
31773
  return out;
31678
31774
  }
31775
+ function readDeliveryMethodsProperty(obj, sf, propertyName) {
31776
+ const value = unwrapAsExpressions(getPropertyInitializer(obj, propertyName));
31777
+ let result;
31778
+ if (!value || isNullishLiteral(value)) {
31779
+ result = { methods: void 0, source: "default" };
31780
+ } else if (Node28.isIdentifier(value)) {
31781
+ result = readDeliveryMethodsArray(asArrayLiteral(findLocalVariable(sf, value.getText())?.getInitializer()));
31782
+ } else {
31783
+ result = readDeliveryMethodsArray(asArrayLiteral(value));
31784
+ }
31785
+ return result;
31786
+ }
31787
+ function readDeliveryMethodsArray(arr) {
31788
+ const methods = [];
31789
+ let readable = arr !== void 0;
31790
+ for (const el of arr?.getElements() ?? []) {
31791
+ const method = readDeliveryMethodElement(unwrapAsExpressions(el));
31792
+ if (!method) {
31793
+ readable = false;
31794
+ } else if (!methods.includes(method)) {
31795
+ methods.push(method);
31796
+ }
31797
+ }
31798
+ const result = readable ? { methods, source: "declared" } : { methods: void 0, source: "unresolved" };
31799
+ return result;
31800
+ }
31801
+ function readDeliveryMethodElement(el) {
31802
+ let result;
31803
+ if (el && Node28.isPropertyAccessExpression(el) && el.getExpression().getText() === DELIVERY_METHOD_ENUM) {
31804
+ result = DELIVERY_METHOD_BY_MEMBER.get(el.getName());
31805
+ } else if (el && Node28.isStringLiteral(el)) {
31806
+ result = DELIVERY_METHOD_BY_CODE.get(el.getLiteralText());
31807
+ }
31808
+ return result;
31809
+ }
31810
+ function isNullishLiteral(node) {
31811
+ return node !== void 0 && (Node28.isIdentifier(node) && node.getText() === "undefined" || node.getKind() === SyntaxKind12.NullKeyword);
31812
+ }
31679
31813
  function extractTemplateInfoAggregates(sources) {
31680
31814
  const out = [];
31681
31815
  for (const sf of sources) {
@@ -32037,6 +32171,7 @@ function collectFromSingleEntryFactory(name, out, index) {
32037
32171
  out.push({
32038
32172
  typeIdentifier: typeIdent,
32039
32173
  factoryFunctionName: name,
32174
+ contentDeliveryMethods: collectContentDeliveryMethods([fn.node, factoryPropertyFunctionNode(obj, index)]),
32040
32175
  sourceFile: fn.relPath,
32041
32176
  line: obj.getStartLineNumber()
32042
32177
  });
@@ -32063,7 +32198,7 @@ function consumeArrayEntryFactoryElement(options) {
32063
32198
  chaseDirectSingleEntryFactoryCall(inner, out, index);
32064
32199
  return;
32065
32200
  }
32066
- recordInlineEntryFactoryObject({ inner, factoryName, relPath, out });
32201
+ recordInlineEntryFactoryObject({ inner, factoryName, relPath, out, index });
32067
32202
  }
32068
32203
  function chaseSpreadEntryFactoryCall(inner, out, index) {
32069
32204
  if (!Node28.isCallExpression(inner)) return;
@@ -32080,12 +32215,40 @@ function chaseDirectSingleEntryFactoryCall(inner, out, index) {
32080
32215
  }
32081
32216
  }
32082
32217
  function recordInlineEntryFactoryObject(options) {
32083
- const { inner, factoryName, relPath, out } = options;
32218
+ const { inner, factoryName, relPath, out, index } = options;
32084
32219
  const obj = asObjectLiteral(inner);
32085
32220
  if (!obj) return;
32086
32221
  const typeIdent = readIdentifierProperty(obj, "type");
32087
32222
  if (!typeIdent) return;
32088
- out.push({ typeIdentifier: typeIdent, factoryFunctionName: factoryName, sourceFile: relPath, line: obj.getStartLineNumber() });
32223
+ const contentDeliveryMethods = collectContentDeliveryMethods([obj, factoryPropertyFunctionNode(obj, index)]);
32224
+ out.push({ typeIdentifier: typeIdent, factoryFunctionName: factoryName, contentDeliveryMethods, sourceFile: relPath, line: obj.getStartLineNumber() });
32225
+ }
32226
+ function factoryPropertyFunctionNode(obj, index) {
32227
+ const name = readIdentifierProperty(obj, "factory");
32228
+ return name ? index.functionsByName.get(name)?.node : void 0;
32229
+ }
32230
+ function collectContentDeliveryMethods(nodes) {
32231
+ const found = /* @__PURE__ */ new Set();
32232
+ for (const node of nodes) {
32233
+ if (node) {
32234
+ scanContentDeliveryMethods(node, found);
32235
+ }
32236
+ }
32237
+ return Array.from(CONTENT_PROPERTY_DELIVERY_METHODS.values()).filter((method) => found.has(method));
32238
+ }
32239
+ function scanContentDeliveryMethods(node, found) {
32240
+ for (const prop of node.getDescendantsOfKind(SyntaxKind12.PropertyAssignment)) {
32241
+ const method = CONTENT_PROPERTY_DELIVERY_METHODS.get(prop.getName());
32242
+ if (method && !isNullishLiteral(unwrapAsExpressions(prop.getInitializer()))) {
32243
+ found.add(method);
32244
+ }
32245
+ }
32246
+ for (const prop of node.getDescendantsOfKind(SyntaxKind12.ShorthandPropertyAssignment)) {
32247
+ const method = CONTENT_PROPERTY_DELIVERY_METHODS.get(prop.getName());
32248
+ if (method) {
32249
+ found.add(method);
32250
+ }
32251
+ }
32089
32252
  }
32090
32253
  function extractTaskServiceCalls(sources, index, trustedExternal) {
32091
32254
  const out = [];
@@ -32373,6 +32536,8 @@ function runRules6(inspection, extracted) {
32373
32536
  checkTemplateInfoPairing(extracted, violations);
32374
32537
  checkTemplateInfoRecord(extracted, violations);
32375
32538
  checkTemplateHandlerFactory(extracted, violations);
32539
+ checkTemplateDeliveryMethods(extracted, violations);
32540
+ checkTemplateForcedDeliveryMethods(extracted, violations);
32376
32541
  checkTasks(extracted, violations);
32377
32542
  checkDuplicates2(extracted, violations);
32378
32543
  return violations;
@@ -32541,6 +32706,63 @@ function checkTemplateHandlerFactory(extracted, violations) {
32541
32706
  });
32542
32707
  }
32543
32708
  }
32709
+ function checkTemplateDeliveryMethods(extracted, violations) {
32710
+ const infoByTypeConstant = /* @__PURE__ */ new Map();
32711
+ for (const info of extracted.templateTypeInfos) {
32712
+ if (info.typeConstantName) infoByTypeConstant.set(info.typeConstantName, info);
32713
+ }
32714
+ for (const entry of extracted.templateHandlerEntries) {
32715
+ const info = infoByTypeConstant.get(entry.typeIdentifier);
32716
+ const userConfigurable = info ? staticUserConfigurableDeliveryMethods(info) : void 0;
32717
+ if (!info || !userConfigurable) continue;
32718
+ const forced = info.forcedDeliveryMethods ?? [];
32719
+ const listed = [...userConfigurable, ...forced.filter((method) => !userConfigurable.includes(method))];
32720
+ const unlisted = entry.contentDeliveryMethods.filter((method) => !listed.includes(method));
32721
+ if (unlisted.length === 0) continue;
32722
+ const listedText = listed.length > 0 ? listed.join(", ") : "none";
32723
+ const forcedSuffix = forced.length > 0 ? " and `forcedDeliveryMethods`" : "";
32724
+ const listedIn = info.userConfigurableDeliveryMethodsSource === "declared" ? `\`userConfigurableDeliveryMethods\`${forcedSuffix}` : "`forcedDeliveryMethods` (it leaves `userConfigurableDeliveryMethods` out, so it is only sent by its forced methods)";
32725
+ pushViolation8(violations, {
32726
+ code: "NOTIF_TEMPLATE_FACTORY_UNLISTED_DELIVERY_METHOD",
32727
+ severity: "warning",
32728
+ message: `Template handler factory \`${entry.factoryFunctionName ?? "<anonymous>"}\` for \`${entry.typeIdentifier}\` builds content for ${unlisted.join(", ")}, but \`${info.symbolName}\` only lists ${listedText} in ${listedIn}. This is the build-time counterpart of the send pipeline's runtime unlisted-delivery-method warning.`,
32729
+ side: "api",
32730
+ file: entry.sourceFile
32731
+ });
32732
+ }
32733
+ }
32734
+ function staticUserConfigurableDeliveryMethods(info) {
32735
+ let result;
32736
+ if (info.forcedDeliveryMethodsSource !== "unresolved") {
32737
+ if (info.userConfigurableDeliveryMethodsSource === "declared") {
32738
+ result = info.userConfigurableDeliveryMethods;
32739
+ } else if (info.userConfigurableDeliveryMethodsSource === "default" && (info.forcedDeliveryMethods ?? []).some((method) => method !== "TEXT")) {
32740
+ result = [];
32741
+ }
32742
+ }
32743
+ return result;
32744
+ }
32745
+ function checkTemplateForcedDeliveryMethods(extracted, violations) {
32746
+ for (const info of extracted.templateTypeInfos) {
32747
+ const forced = info.forcedDeliveryMethodsSource === "declared" ? info.forcedDeliveryMethods ?? [] : [];
32748
+ if (forced.includes("TEXT")) {
32749
+ pushViolation8(violations, {
32750
+ code: "NOTIF_TEMPLATE_FORCED_TEXT_DELIVERY_METHOD",
32751
+ message: `Info \`${info.symbolName}\` lists TEXT in \`forcedDeliveryMethods\`. Texts cannot be forced; the info record throws at startup.`,
32752
+ side: "component",
32753
+ file: info.sourceFile
32754
+ });
32755
+ }
32756
+ if (forced.length > 0 && info.onlySendToExplicitlyEnabledRecipients === true) {
32757
+ pushViolation8(violations, {
32758
+ code: "NOTIF_TEMPLATE_FORCED_DELIVERY_METHOD_EXPLICIT_OPT_IN",
32759
+ message: `Info \`${info.symbolName}\` forces ${forced.join(", ")} while \`onlySendToExplicitlyEnabledRecipients\` is true. A forced method is on by default, so the info record throws at startup.`,
32760
+ side: "component",
32761
+ file: info.sourceFile
32762
+ });
32763
+ }
32764
+ }
32765
+ }
32544
32766
  function checkTasks(extracted, violations) {
32545
32767
  if (extracted.taskTypeConstants.length === 0) return;
32546
32768
  flagTasksMissingFromAllArrays(extracted, violations);
@@ -32687,8 +32909,7 @@ function flagValidateWithoutHandler(options) {
32687
32909
  if (!lenientHandlerTypeIdentifiers.has(name) && !extracted.trustedExternalIdentifiers.has(name)) {
32688
32910
  pushViolation8(violations, {
32689
32911
  code: "NOTIF_TASK_IN_VALIDATE_WITHOUT_HANDLER",
32690
- severity: "warning",
32691
- message: `Task type \`${name}\` appears in \`notificationTaskService({ validate })\` but has no matching handler in the \`handlers\` array.`,
32912
+ message: `Task type \`${name}\` appears in \`notificationTaskService({ validate })\` but has no matching handler in the \`handlers\` array. \`notificationTaskService()\` throws at startup for this.`,
32692
32913
  side: "api",
32693
32914
  file: call.sourceFile
32694
32915
  });
@@ -32874,12 +33095,11 @@ async function inspectAppNotifications(componentDir, apiDir) {
32874
33095
  }
32875
33096
 
32876
33097
  // packages/dbx-cli/validate/src/lib/notification-m-validate-app/index.ts
32877
- function validateAppNotifications(inspection, options) {
32878
- const extracted = extractAppNotifications(inspection);
33098
+ function validateExtractedAppNotifications(input) {
32879
33099
  const violations = [];
32880
33100
  let errorCount = 0;
32881
33101
  let warningCount = 0;
32882
- for (const v of runRules6(inspection, extracted)) {
33102
+ for (const v of runRules6(input.inspection, input.extracted)) {
32883
33103
  violations.push(v);
32884
33104
  if (v.severity === "error") {
32885
33105
  errorCount += 1;
@@ -32891,11 +33111,14 @@ function validateAppNotifications(inspection, options) {
32891
33111
  violations,
32892
33112
  errorCount,
32893
33113
  warningCount,
32894
- componentDir: options.componentDir,
32895
- apiDir: options.apiDir
33114
+ componentDir: input.componentDir,
33115
+ apiDir: input.apiDir
32896
33116
  };
32897
33117
  return result;
32898
33118
  }
33119
+ function validateAppNotifications(inspection, options) {
33120
+ return validateExtractedAppNotifications({ inspection, extracted: extractAppNotifications(inspection), componentDir: options.componentDir, apiDir: options.apiDir });
33121
+ }
32899
33122
 
32900
33123
  // packages/dbx-cli/validate/src/lib/notification-m-validate-folder/index.ts
32901
33124
  var notification_m_validate_folder_exports = {};
@@ -38012,6 +38235,7 @@ function listAppAssets(inspection, options) {
38012
38235
  // packages/dbx-cli/validate/src/lib/notification-m-list-app/index.ts
38013
38236
  var notification_m_list_app_exports = {};
38014
38237
  __export(notification_m_list_app_exports, {
38238
+ collectAppNotifications: () => collectAppNotifications,
38015
38239
  formatReportAsJson: () => formatReportAsJson5,
38016
38240
  formatReportAsMarkdown: () => formatReportAsMarkdown5,
38017
38241
  listAppNotifications: () => listAppNotifications
@@ -38043,8 +38267,12 @@ function buildTemplateSummaries(extracted) {
38043
38267
  const record = extracted.templateInfoRecord;
38044
38268
  const resolved = new Set(record ? record.resolvedInfoIdentifiers : []);
38045
38269
  const handlerByType = /* @__PURE__ */ new Map();
38270
+ const handlerEntriesByType = /* @__PURE__ */ new Map();
38046
38271
  for (const entry of extracted.templateHandlerEntries) {
38047
38272
  handlerByType.set(entry.typeIdentifier, { factoryFunctionName: entry.factoryFunctionName });
38273
+ const entries = handlerEntriesByType.get(entry.typeIdentifier) ?? [];
38274
+ entries.push(entry);
38275
+ handlerEntriesByType.set(entry.typeIdentifier, entries);
38048
38276
  }
38049
38277
  const templates = [];
38050
38278
  for (const constant of extracted.templateTypeConstants) {
@@ -38056,8 +38284,13 @@ function buildTemplateSummaries(extracted) {
38056
38284
  infoSymbolName: info?.symbolName,
38057
38285
  humanName: info?.humanName,
38058
38286
  description: info?.description,
38059
- notificationMIdentity: info?.notificationMIdentity,
38287
+ notificationModelIdentity: info?.notificationModelIdentity,
38060
38288
  targetModelIdentity: info?.targetModelIdentity,
38289
+ userConfigurableDeliveryMethods: info?.userConfigurableDeliveryMethods,
38290
+ userConfigurableDeliveryMethodsSource: info?.userConfigurableDeliveryMethodsSource,
38291
+ forcedDeliveryMethods: info?.forcedDeliveryMethods,
38292
+ forcedDeliveryMethodsSource: info?.forcedDeliveryMethodsSource,
38293
+ factoryContentDeliveryMethods: unionContentDeliveryMethods(handlerEntriesByType.get(constant.symbolName) ?? []),
38061
38294
  inInfoRecord: info ? resolved.has(info.symbolName) : false,
38062
38295
  hasFactory: handler !== void 0,
38063
38296
  factoryFunctionName: handler?.factoryFunctionName,
@@ -38066,6 +38299,15 @@ function buildTemplateSummaries(extracted) {
38066
38299
  }
38067
38300
  return templates;
38068
38301
  }
38302
+ function unionContentDeliveryMethods(entries) {
38303
+ const result = [];
38304
+ for (const entry of entries) {
38305
+ for (const method of entry.contentDeliveryMethods) {
38306
+ if (!result.includes(method)) result.push(method);
38307
+ }
38308
+ }
38309
+ return result;
38310
+ }
38069
38311
  function buildTaskSummaries(extracted) {
38070
38312
  const indices = buildTaskIndices(extracted);
38071
38313
  const tasks = [];
@@ -38193,13 +38435,45 @@ function formatTemplateBlock(t) {
38193
38435
  parts.push(heading);
38194
38436
  if (t.humanName) parts.push(`- Human name: ${t.humanName}`);
38195
38437
  if (t.description) parts.push(`- Description: ${t.description}`);
38196
- if (t.notificationMIdentity) parts.push(`- Notification model: \`${t.notificationMIdentity}\``);
38438
+ if (t.notificationModelIdentity) parts.push(`- Notification model: \`${t.notificationModelIdentity}\``);
38197
38439
  if (t.targetModelIdentity) parts.push(`- Target model: \`${t.targetModelIdentity}\``);
38440
+ const deliveryMethodsText = formatUserConfigurableDeliveryMethods(t);
38441
+ if (deliveryMethodsText) parts.push(`- User-configurable delivery methods: ${deliveryMethodsText}`);
38442
+ const forcedDeliveryMethodsText = formatForcedDeliveryMethods(t);
38443
+ if (forcedDeliveryMethodsText) parts.push(`- Forced (always on) delivery methods: ${forcedDeliveryMethodsText}`);
38444
+ if (t.factoryContentDeliveryMethods.length > 0) parts.push(`- Factory returns content for: ${t.factoryContentDeliveryMethods.join(", ")}`);
38198
38445
  const infoText = t.infoSymbolName ? code6(t.infoSymbolName) : "_Missing._";
38199
38446
  const factorySuffix = t.factoryFunctionName ? ` (${code6(t.factoryFunctionName)})` : "";
38200
38447
  parts.push(`- Info object: ${infoText}`, `- In info record: ${formatBool2(t.inInfoRecord)}`, `- Has factory: ${formatBool2(t.hasFactory)}${factorySuffix}`, `- Source: \`${t.sourceFile}\``);
38201
38448
  return parts.join("\n");
38202
38449
  }
38450
+ function formatUserConfigurableDeliveryMethods(t) {
38451
+ let result;
38452
+ switch (t.userConfigurableDeliveryMethodsSource) {
38453
+ case "declared":
38454
+ result = t.userConfigurableDeliveryMethods && t.userConfigurableDeliveryMethods.length > 0 ? t.userConfigurableDeliveryMethods.join(", ") : "_none_";
38455
+ break;
38456
+ case "default":
38457
+ result = t.forcedDeliveryMethodsSource === "declared" && (t.forcedDeliveryMethods ?? []).some((method) => method !== "TEXT") ? "_none_ (the default when the info forces a method)" : "default";
38458
+ break;
38459
+ case "unresolved":
38460
+ result = "_unresolved_";
38461
+ break;
38462
+ case void 0:
38463
+ result = void 0;
38464
+ break;
38465
+ }
38466
+ return result;
38467
+ }
38468
+ function formatForcedDeliveryMethods(t) {
38469
+ let result;
38470
+ if (t.forcedDeliveryMethodsSource === "declared" && t.forcedDeliveryMethods && t.forcedDeliveryMethods.length > 0) {
38471
+ result = t.forcedDeliveryMethods.join(", ");
38472
+ } else if (t.forcedDeliveryMethodsSource === "unresolved") {
38473
+ result = "_unresolved_";
38474
+ }
38475
+ return result;
38476
+ }
38203
38477
  function formatTaskBlock(t) {
38204
38478
  const parts = [];
38205
38479
  const heading = t.typeCode ? `### ${t.typeCode} \u2014 \`${t.symbolName}\`` : `### \`${t.symbolName}\``;
@@ -44619,6 +44893,8 @@ var DBX_NOTIFICATION_M_VALIDATE_APP_TOOL = {
44619
44893
  "",
44620
44894
  "External identifiers imported from `@dereekb/*` are trusted \u2014 spreads and handler `type:` references that resolve into upstream packages do not produce orphan/unresolved errors.",
44621
44895
  "",
44896
+ "The same checks run at build time through `dbx-cli-generate-notification-manifest` (see the `@dereekb/dbx-cli` README); this tool always scans the current source.",
44897
+ "",
44622
44898
  "Provide both:",
44623
44899
  "- `componentDir`: relative path to the `-firebase` component package (e.g. `components/demo-firebase`).",
44624
44900
  "- `apiDir`: relative path to the API app (e.g. `apps/demo-api`).",
@@ -44655,7 +44931,9 @@ var { formatReportAsJson: formatReportAsJson11, formatReportAsMarkdown: formatRe
44655
44931
  var DBX_NOTIFICATION_M_LIST_APP_TOOL = {
44656
44932
  name: "dbx_notification_m_list_app",
44657
44933
  description: [
44658
- "List every notification template + notification task configured in a downstream `-firebase` component + API app pair. Each entry reports its type code, metadata (human name, description, notification/target model identity), and registration flags \u2014 `inInfoRecord` + `hasFactory` for templates, `inAllArray` + `inValidateList` + `hasHandler` for tasks.",
44934
+ "List every notification template + notification task configured in a downstream `-firebase` component + API app pair. Each entry reports its type code, metadata (human name, description, notification/target model identity), and registration flags \u2014 `inInfoRecord` + `hasFactory` for templates, `inAllArray` + `inValidateList` + `hasHandler` for tasks. Templates also report `userConfigurableDeliveryMethods` (with a `default` / `declared` / `unresolved` source), `forcedDeliveryMethods` (always-on methods, with the same kind of source) and `factoryContentDeliveryMethods`, the channels their factory builds content for.",
44935
+ "",
44936
+ "Its JSON is the report part of the `notification.manifest.json` that `dbx-cli-generate-notification-manifest` writes at build time; this tool always scans the current source.",
44659
44937
  "",
44660
44938
  "Cross-file resolution mirrors `dbx_notification_m_validate_app` \u2014 spreads and factory composition are traced through every `.ts` file under `src/lib/model/notification/` on the component side and `src/app/common/model/notification/` + `src/app/common/firebase/` on the API side.",
44661
44939
  "",
@@ -51420,7 +51698,7 @@ export const <<SCREAMING>>_NOTIFICATION_TEMPLATE_TYPE_INFO: NotificationTemplate
51420
51698
  type: <<SCREAMING>>_NOTIFICATION_TEMPLATE_TYPE,
51421
51699
  name: '<<Pascal>>',
51422
51700
  description: 'TODO: describe the <<Pascal>> notification.',
51423
- notificationMIdentity: profileIdentity // TODO: replace with the model identity this notification targets
51701
+ notificationModelIdentity: profileIdentity // TODO: replace with the model identity this notification targets
51424
51702
  };
51425
51703
 
51426
51704
  export interface <<Pascal>>NotificationData {
@@ -52075,6 +52353,31 @@ var STORAGEFILE_PROCESSOR_SUBTASK = {
52075
52353
  seeAlso: ["storagefile-processor", "storagefile-purpose"],
52076
52354
  verify: "Run `dbx_validate_app_storagefiles` \u2014 `STORAGEFILE_PROCESSING_SUBTASK_NOT_HANDLED` should clear once the flow entry is added."
52077
52355
  };
52356
+ var NOTIFICATION_BUILD_TIME_CHECK_STEP = {
52357
+ heading: "Build-time check",
52358
+ path: "<apiDir>/project.json",
52359
+ body: [
52360
+ "Wire `dbx-cli-generate-notification-manifest` (from `@dereekb/dbx-cli`) as an nx target so broken notification wiring fails the build before deploy. It runs the same rules as `dbx_notification_m_validate_app`, writes `notification.manifest.json`, and exits non-zero on a validator error (warnings are written into the manifest). One target covers every template and task type; add it once per app.",
52361
+ "",
52362
+ "```json",
52363
+ '"generate-notification-manifest": {',
52364
+ ' "executor": "nx:run-commands",',
52365
+ ' "outputs": ["{workspaceRoot}/dist/<apiDir>/notification.manifest.json"],',
52366
+ ' "inputs": [',
52367
+ ' "{workspaceRoot}/<componentDir>/src/lib/model/notification/**/*.ts",',
52368
+ ' "{workspaceRoot}/<apiDir>/src/app/common/model/notification/**/*.ts",',
52369
+ ' "{workspaceRoot}/<apiDir>/src/app/common/firebase/**/*.ts"',
52370
+ " ],",
52371
+ ' "options": {',
52372
+ ' "command": "npx dbx-cli-generate-notification-manifest --component-dir=<componentDir> --api-dir=<apiDir> --output=dist/<apiDir>/notification.manifest.json",',
52373
+ ' "cwd": "{workspaceRoot}"',
52374
+ " }",
52375
+ "}",
52376
+ "```",
52377
+ "",
52378
+ "**Wire into:** the API project's `build.dependsOn` (not `serve`: nothing reads the manifest at runtime, and the server's startup checks already cover `serve`)."
52379
+ ].join("\n")
52380
+ };
52078
52381
  var NOTIFICATION_TEMPLATE = {
52079
52382
  artifact: "notification-template",
52080
52383
  title: "Notification template",
@@ -52087,7 +52390,7 @@ var NOTIFICATION_TEMPLATE = {
52087
52390
  body: [
52088
52391
  "**Required exports:**",
52089
52392
  "- `<NAME>_NOTIFICATION_TEMPLATE_TYPE: NotificationTemplateType = '<short_code>'`.",
52090
- "- `<NAME>_NOTIFICATION_TEMPLATE_TYPE_INFO: NotificationTemplateTypeInfo` with `{ type, name, description, notificationMIdentity, targetModelIdentity? }`.",
52393
+ "- `<NAME>_NOTIFICATION_TEMPLATE_TYPE_INFO: NotificationTemplateTypeInfo` with `{ type, name, description, notificationModelIdentity, targetModelIdentity? }`.",
52091
52394
  "- For multi-file workspaces, also declare `ALL_<MODULE>_NOTIFICATION_TEMPLATE_TYPE_INFOS: NotificationTemplateTypeInfo[]` and re-export the per-file infos.",
52092
52395
  "",
52093
52396
  "**Barrel:** Re-export from `<componentDir>/src/lib/model/notification/index.ts`."
@@ -52109,10 +52412,11 @@ var NOTIFICATION_TEMPLATE = {
52109
52412
  "",
52110
52413
  "**Wire into:** the top-level `<app>NotificationTemplateServiceConfigsArrayFactory(context)` \u2014 call the new factory inside its returned array. The configs-array factory is itself bound via `NOTIFICATION_TEMPLATE_SERVICE_CONFIGS_ARRAY_TOKEN` in the notification module."
52111
52414
  ].join("\n")
52112
- }
52415
+ },
52416
+ NOTIFICATION_BUILD_TIME_CHECK_STEP
52113
52417
  ],
52114
52418
  seeAlso: ["notification-task"],
52115
- verify: "Run `dbx_notification_m_validate_app` to confirm both registration paths (info-record + handler factory) are wired, then `dbx_notification_m_list_app` to confirm the new template appears with `inInfoRecord: true` and `hasFactory: true`."
52419
+ verify: "Run `dbx_notification_m_validate_app` to confirm both registration paths (info-record + handler factory) are wired, then `dbx_notification_m_list_app` to confirm the new template appears with `inInfoRecord: true` and `hasFactory: true`, then `npx nx run <api-project>:generate-notification-manifest` runs the same checks the build does."
52116
52420
  };
52117
52421
  var NOTIFICATION_TASK = {
52118
52422
  artifact: "notification-task",
@@ -52152,10 +52456,11 @@ var NOTIFICATION_TASK = {
52152
52456
  {
52153
52457
  heading: "Wire into task service",
52154
52458
  body: ["Push the new handler config into the `handlers: [...]` array passed to `notificationTaskService({ validate, handlers })`. The `validate:` spread should already cover it via `ALL_NOTIFICATION_TASK_TYPES`."].join("\n")
52155
- }
52459
+ },
52460
+ NOTIFICATION_BUILD_TIME_CHECK_STEP
52156
52461
  ],
52157
52462
  seeAlso: ["notification-template"],
52158
- verify: "Run `dbx_notification_m_validate_app` to confirm `inAllArray: true` + `hasHandler: true` for the new task type."
52463
+ verify: "Run `dbx_notification_m_validate_app` to confirm `inAllArray: true` + `hasHandler: true` for the new task type, then `npx nx run <api-project>:generate-notification-manifest` runs the same checks the build does."
52159
52464
  };
52160
52465
  var NESTJS_MODEL_MODULE = {
52161
52466
  artifact: "nestjs-model-module",
@@ -93,13 +93,13 @@
93
93
  {
94
94
  "name": "buttonStyle",
95
95
  "type": "Maybe<DbxButtonStyle>",
96
- "description": "",
96
+ "description": "Style of the button. Individual style inputs (e.g. {@link color}, {@link raised}) take precedence.\n\nDeclared as a `model` so a directive composed onto the button (e.g. `dbxRotatingButton`) can set it.",
97
97
  "required": false
98
98
  },
99
99
  {
100
100
  "name": "color",
101
101
  "type": "Maybe<ThemePalette | DbxColorInput>",
102
- "description": "",
102
+ "description": "Color of the button. Takes precedence over the {@link buttonStyle} color.\n\nDeclared as a `model` so a directive composed onto the button (e.g. `dbxRotatingButton`) can set it.",
103
103
  "required": false
104
104
  },
105
105
  {
@@ -854,6 +854,36 @@
854
854
  "example": "<dbx-progress-spinner-button [config]=\"cfg\" (btnClick)=\"onClick()\"></dbx-progress-spinner-button>",
855
855
  "minimalExample": "<dbx-progress-spinner-button [config]=\"cfg\"></dbx-progress-spinner-button>"
856
856
  },
857
+ {
858
+ "slug": "rotating-button",
859
+ "category": "button",
860
+ "kind": "directive",
861
+ "selector": "[dbxRotatingButton]",
862
+ "className": "DbxRotatingButtonDirective",
863
+ "module": "@dereekb/dbx-web",
864
+ "description": "Turns a `dbx-button` into a rotating / multi-state button. Each click moves to the next configured state, and the\nstate's icon, text, style and aria-label are set on the button.\n\nUse {@link dbxTristateRotatingButtonConfig} for a three-state / 3-phase (default/on/off) toggle. The directive sets the\nbutton's `icon`, `text`, `buttonStyle` and `ariaLabel`, so configure those through the config rather than on the button.\n\nThe button stops click propagation by default, so a rotating button works inside clickable list rows.",
865
+ "inputs": [
866
+ {
867
+ "name": "dbxRotatingButton",
868
+ "type": "Maybe<DbxRotatingButtonConfig<T>>",
869
+ "description": "",
870
+ "required": false
871
+ },
872
+ {
873
+ "name": "dbxRotatingButtonValue",
874
+ "type": "Maybe<T>",
875
+ "description": "The current value. Two-way bindable with `[(dbxRotatingButtonValue)]`.",
876
+ "required": false
877
+ }
878
+ ],
879
+ "outputs": [],
880
+ "relatedSlugs": [
881
+ "button",
882
+ "icon-button"
883
+ ],
884
+ "example": "readonly tristateConfig = dbxTristateRotatingButtonConfig({ label: 'Text', defaultValue: false });",
885
+ "minimalExample": "<dbx-button iconOnly [dbxRotatingButton]=\"tristateConfig\" [(dbxRotatingButtonValue)]=\"emailEnabled\"></dbx-button>"
886
+ },
857
887
  {
858
888
  "slug": "browser-agent-view",
859
889
  "category": "misc",
@@ -874,6 +904,12 @@
874
904
  "type": "Maybe<boolean>",
875
905
  "description": "",
876
906
  "required": false
907
+ },
908
+ {
909
+ "name": "overrideKey",
910
+ "type": "Maybe<DbxBrowserAgentViewEntryKey>",
911
+ "description": "Key of the manually selected entry. Can be bound two-way to persist the user's choice.",
912
+ "required": false
877
913
  }
878
914
  ],
879
915
  "outputs": [],
@@ -1033,7 +1069,7 @@
1033
1069
  "selector": "dbx-prompt-confirm",
1034
1070
  "className": "DbxPromptConfirmComponent",
1035
1071
  "module": "@dereekb/dbx-web",
1036
- "description": "Renders a confirmation prompt with customizable title, message, confirm, and cancel buttons.",
1072
+ "description": "Renders a confirmation prompt with customizable title, message, confirm, and cancel buttons.\n\nProjected content shows above the buttons. Content marked with the `buttons` attribute shows in the same row, before the\nconfirm button.",
1037
1073
  "inputs": [
1038
1074
  {
1039
1075
  "name": "config",
@@ -1055,7 +1091,7 @@
1055
1091
  "description": ""
1056
1092
  }
1057
1093
  ],
1058
- "contentProjection": "<ng-content></ng-content>",
1094
+ "contentProjection": "<ng-content></ng-content>; <ng-content select=\"[buttons]\"></ng-content>",
1059
1095
  "relatedSlugs": [
1060
1096
  "prompt",
1061
1097
  "action-confirm"
@@ -2121,7 +2157,20 @@
2121
2157
  "className": "DbxColorDirective",
2122
2158
  "module": "@dereekb/dbx-web",
2123
2159
  "description": "Provides themed color *tokens* to the host element — it never paints a background itself.\n\nAccepts either a {@link DbxThemeColor} string (e.g. `'primary'`, `'success'`) or a {@link DbxColorConfig}\nobject carrying arbitrary CSS color values (any hex, `rgb()`, `hsl()`, `var(...)`, `color-mix(...)`, etc.).\nThe directive sets the `--dbx-bg-color-current` / `--dbx-color-current` (and tone) custom properties and adds the\n`.dbx-color` marker class, leaving the actual painting to either the explicit `.dbx-color-bg` utility on the same\nelement or a colored-surface component's own `.dbx-color`-scoped SCSS (e.g. `<dbx-bar>`, `<dbx-button>`, `<dbx-loading>`,\n`<dbx-icon-tile>`, `<mat-card>`) that reads the inherited tokens.\n\nOptionally set {@link dbxColorTone} (0-100) to control background opacity for a tonal/muted appearance.\nWhen tonal mode is active, the `dbx-color-tonal` CSS class is added and a CSS rule overrides the text\ncolor to the vibrant color (via `--dbx-bg-color-current`).\n\nThe standalone `[dbxColorTone]` input wins over `config.tone` when both are set; the same precedence\napplies to tonal mode.",
2124
- "inputs": [],
2160
+ "inputs": [
2161
+ {
2162
+ "name": "dbxColor",
2163
+ "type": "Maybe<DbxColorInput>",
2164
+ "description": "The color to provide tokens for. Declared as a `model` so a colored-surface component on the same host (e.g.\n`dbx-button`) can push its resolved color into the directive, keeping it the single token provider on the element.",
2165
+ "required": false
2166
+ },
2167
+ {
2168
+ "name": "dbxColorTone",
2169
+ "type": "Maybe<DbxColorTone>",
2170
+ "description": "Background tone level (0-100). When set, the background becomes semi-transparent\nand text color switches to the vibrant theme color for a tonal appearance.\n\nDeclared as a `model` for the same reason as {@link dbxColor}: a colored-surface component on the same host (e.g.\n`dbx-avatar-view`) resolves its tone from its own inputs/context and pushes it here, keeping this directive the\nsingle token provider on the element.",
2171
+ "required": false
2172
+ }
2173
+ ],
2125
2174
  "outputs": [],
2126
2175
  "relatedSlugs": [
2127
2176
  "text-color",
@@ -513,6 +513,125 @@
513
513
  "derivedComposites": [],
514
514
  "derivedFieldOverrides": []
515
515
  },
516
+ {
517
+ "slug": "notification-users-with-text-phone-number-query",
518
+ "name": "notificationUsersWithTextPhoneNumberQuery",
519
+ "module": "@dereekb/firebase",
520
+ "subpath": "model/notification/notification.query",
521
+ "signature": "notificationUsersWithTextPhoneNumberQuery(phoneNumber: E164PhoneNumber): FirestoreQueryConstraint[]",
522
+ "description": "Query constraints for finding {@link NotificationUser} documents whose texting number (`gc.t`) is the given phone number.\n\nUsed by the text opt-out sync to find the users a STOP reply came from.",
523
+ "model": "NotificationUser",
524
+ "collection": "nu",
525
+ "isNested": false,
526
+ "scope": "COLLECTION",
527
+ "manual": false,
528
+ "skip": false,
529
+ "category": "lookup",
530
+ "params": [
531
+ {
532
+ "name": "phoneNumber",
533
+ "type": "E164PhoneNumber",
534
+ "description": "- The texting number to match.",
535
+ "optional": false
536
+ }
537
+ ],
538
+ "returns": "Array of Firestore query constraints filtering for users with that texting number.",
539
+ "tags": [
540
+ "lookup",
541
+ "notificationuser",
542
+ "notification",
543
+ "users",
544
+ "with",
545
+ "text",
546
+ "phone",
547
+ "number",
548
+ "query",
549
+ "notificationuserswithtextphonenumberquery",
550
+ "user",
551
+ "constraints",
552
+ "finding",
553
+ "link",
554
+ "documents",
555
+ "whose",
556
+ "texting",
557
+ "given",
558
+ "used"
559
+ ],
560
+ "constraintSequences": [
561
+ {
562
+ "pathLabel": "all",
563
+ "entries": [
564
+ {
565
+ "kind": "where",
566
+ "fieldPath": "gc.t",
567
+ "operator": "=="
568
+ }
569
+ ]
570
+ }
571
+ ],
572
+ "derivedComposites": [],
573
+ "derivedFieldOverrides": []
574
+ },
575
+ {
576
+ "slug": "notification-users-with-stopped-text-phone-number-query",
577
+ "name": "notificationUsersWithStoppedTextPhoneNumberQuery",
578
+ "module": "@dereekb/firebase",
579
+ "subpath": "model/notification/notification.query",
580
+ "signature": "notificationUsersWithStoppedTextPhoneNumberQuery(phoneNumber: E164PhoneNumber): FirestoreQueryConstraint[]",
581
+ "description": "Query constraints for finding {@link NotificationUser} documents that have the given phone number in their stopped numbers (`tso`).\n\nUsed by the text opt-out sync to find the users a START reply turns texts back on for.",
582
+ "model": "NotificationUser",
583
+ "collection": "nu",
584
+ "isNested": false,
585
+ "scope": "COLLECTION",
586
+ "manual": false,
587
+ "skip": false,
588
+ "category": "lookup",
589
+ "params": [
590
+ {
591
+ "name": "phoneNumber",
592
+ "type": "E164PhoneNumber",
593
+ "description": "- The stopped phone number to match.",
594
+ "optional": false
595
+ }
596
+ ],
597
+ "returns": "Array of Firestore query constraints filtering for users that stopped that number.",
598
+ "tags": [
599
+ "lookup",
600
+ "notificationuser",
601
+ "notification",
602
+ "users",
603
+ "with",
604
+ "stopped",
605
+ "text",
606
+ "phone",
607
+ "number",
608
+ "query",
609
+ "notificationuserswithstoppedtextphonenumberquery",
610
+ "user",
611
+ "constraints",
612
+ "finding",
613
+ "link",
614
+ "documents",
615
+ "have",
616
+ "given",
617
+ "their",
618
+ "numbers"
619
+ ],
620
+ "constraintSequences": [
621
+ {
622
+ "pathLabel": "all",
623
+ "entries": [
624
+ {
625
+ "kind": "where",
626
+ "fieldPath": "tso",
627
+ "operator": "array-contains"
628
+ }
629
+ ]
630
+ }
631
+ ],
632
+ "derivedComposites": [],
633
+ "derivedFieldOverrides": []
634
+ },
516
635
  {
517
636
  "slug": "notification-summaries-flagged-for-needs-initialization-query",
518
637
  "name": "notificationSummariesFlaggedForNeedsInitializationQuery",
@@ -298,6 +298,20 @@
298
298
  ],
299
299
  "notes": "Names one upload \"slot\" within a FormSpace — the resume, the cover letter, the photo of the ID.\n\nA slot is stored on the uploaded {@link StorageFile} as its `pg` (purposeSubgroup), which is what makes\n\"replace the file in this slot\" the existing `flagPreviousForDelete` behaviour rather than new machinery."
300
300
  },
301
+ {
302
+ "name": "NotificationTemplateTypeInfoGroupKey",
303
+ "package": "@dereekb/firebase",
304
+ "module": "src/lib/model/notification/notification.details",
305
+ "kind": "semantic-type",
306
+ "definition": "string",
307
+ "baseType": "string",
308
+ "topics": [
309
+ "identifier",
310
+ "string",
311
+ "dereekb-firebase:notification"
312
+ ],
313
+ "notes": "Key of a {@link NotificationTemplateTypeInfoGroup}. Template types whose groups share a key are shown together."
314
+ },
301
315
  {
302
316
  "name": "NotificationTemplateType",
303
317
  "package": "@dereekb/firebase",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-components-mcp",
3
- "version": "14.14.0",
3
+ "version": "14.16.0",
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,7 @@
12
12
  "ts-morph": "^21.0.0"
13
13
  },
14
14
  "peerDependencies": {
15
- "@dereekb/dbx-cli": "14.14.0",
16
- "@dereekb/util": "14.14.0"
15
+ "@dereekb/dbx-cli": "14.16.0",
16
+ "@dereekb/util": "14.16.0"
17
17
  }
18
18
  }