forge-openclaw-plugin 0.2.100 → 0.2.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/dist/assets/{activity-page-5oyCFOns.js → activity-page-CgF7K2ww.js} +1 -1
  2. package/dist/assets/ai-surface-workspace-xtB5RFQu.js +1 -0
  3. package/dist/assets/atlas-panel-B3dPHCmZ.js +1 -0
  4. package/dist/assets/{board-BkDRaMp6.js → board-DqHzdCPQ.js} +1 -1
  5. package/dist/assets/{calendar-page-Bo2iua-a.js → calendar-page-C1Wfd2Fy.js} +1 -1
  6. package/dist/assets/{calendar-rules-DA1g3QUk.js → calendar-rules-CaZXtlxt.js} +1 -1
  7. package/dist/assets/calendar-week-toolbar-BZ_-X3Wb.js +1 -0
  8. package/dist/assets/{charts-P7EVhIog.js → charts-FcU0F3XV.js} +8 -8
  9. package/dist/assets/{companion-sync-lab-page-CosNknOK.js → companion-sync-lab-page-NgeK-O-P.js} +1 -1
  10. package/dist/assets/daily-metrics-dashboard-BMyL0Qr4.js +1 -0
  11. package/dist/assets/{entity-note-count-link-BmGDB572.js → entity-note-count-link-BrS1-O0o.js} +1 -1
  12. package/dist/assets/entity-notes-surface-CBylYDwy.js +1 -0
  13. package/dist/assets/{execution-board-CDRXQB85.js → execution-board-CpO2ch6v.js} +1 -1
  14. package/dist/assets/faceted-token-search-D7xPWfOl.js +1 -0
  15. package/dist/assets/flagship-signal-deck-BEFKOhvx.js +1 -0
  16. package/dist/assets/{floating-action-menu-CJkI2iFy.js → floating-action-menu-zzC2r0Ob.js} +1 -1
  17. package/dist/assets/{forms-BFlTgZ3W.js → forms-CmLAyGqz.js} +1 -1
  18. package/dist/assets/{goal-detail-page-cJvHaLMQ.js → goal-detail-page-JK_Nva8e.js} +1 -1
  19. package/dist/assets/goals-page-yeoJ06Vw.js +1 -0
  20. package/dist/assets/{graph-D6JLqDbD.js → graph-BTa79qum.js} +14 -14
  21. package/dist/assets/{habits-page-DKb96_mj.js → habits-page-Dx5EhkJi.js} +1 -1
  22. package/dist/assets/index-BHIKoiZ6.js +19 -0
  23. package/dist/assets/index-H8R-ABM3.css +1 -0
  24. package/dist/assets/insight-flow-dialog-BtIQXXsS.js +1 -0
  25. package/dist/assets/{insights-page-Dc9oFltJ.js → insights-page-CujrosD_.js} +1 -1
  26. package/dist/assets/{kanban-page-JAxerYh6.js → kanban-page-Q9NuIz5w.js} +1 -1
  27. package/dist/assets/knowledge-graph-page-DaJmlvvM.js +1 -0
  28. package/dist/assets/{life-force-page-BGDbQuVh.js → life-force-page-BGDkKfbJ.js} +1 -1
  29. package/dist/assets/{life-force-workspace-B1fYSXRC.js → life-force-workspace-CLVexVnb.js} +1 -1
  30. package/dist/assets/{maps-ClgJoCjz.js → maps-CF1RagUX.js} +1 -1
  31. package/dist/assets/metric-tile-4iMd_WnJ.js +1 -0
  32. package/dist/assets/{motion-BeD44FeG.js → motion-CfdU2F35.js} +1 -1
  33. package/dist/assets/movement-page-ClaoTNuX.js +1 -0
  34. package/dist/assets/note-markdown-CsGQhQXF.js +3 -0
  35. package/dist/assets/note-tags-input-DdZi93tj.js +1 -0
  36. package/dist/assets/notes-page-DYI8s3NN.js +1 -0
  37. package/dist/assets/{open-in-graph-button-Cg5VrKsC.js → open-in-graph-button-C0TGev7c.js} +1 -1
  38. package/dist/assets/orbit-map-DVXkfQdd.js +1 -0
  39. package/dist/assets/overview-page-BwMlFQKX.js +1 -0
  40. package/dist/assets/page-hero-oFHaAjtL.js +1 -0
  41. package/dist/assets/pill-cluster-7UZd_lHp.js +1 -0
  42. package/dist/assets/{preference-entity-handoff-button-D4WAs9pC.js → preference-entity-handoff-button-DOKV9bZc.js} +1 -1
  43. package/dist/assets/preferences-page-BrKEkmfD.js +1 -0
  44. package/dist/assets/{project-collections-DvaX20q_.js → project-collections-CZagCmeH.js} +1 -1
  45. package/dist/assets/{project-detail-page-drPIFZGb.js → project-detail-page-DogvVNEn.js} +1 -1
  46. package/dist/assets/{project-management-hierarchy-page-BUbRXvny.js → project-management-hierarchy-page-Dr7hW9PW.js} +1 -1
  47. package/dist/assets/{project-management-section-nav-C2Ud8Zdd.js → project-management-section-nav-BLdAgTge.js} +1 -1
  48. package/dist/assets/{projects-page-BGzEZUtg.js → projects-page-UKrby_5-.js} +1 -1
  49. package/dist/assets/psyche-behaviors-page-BeVlXvbj.js +5 -0
  50. package/dist/assets/psyche-flashcards-page-CqAWCO4E.js +1 -0
  51. package/dist/assets/psyche-goal-map-page-IyNnbk_W.js +1 -0
  52. package/dist/assets/psyche-graph-IkUQRaDK.js +1 -0
  53. package/dist/assets/{psyche-metrics-page-zYTJDbyZ.js → psyche-metrics-page-uai0a7Lx.js} +1 -1
  54. package/dist/assets/psyche-mode-guide-page-C7p-ABiF.js +1 -0
  55. package/dist/assets/psyche-modes-page-CLQ5V3w0.js +1 -0
  56. package/dist/assets/psyche-page-NQBHkkpU.js +1 -0
  57. package/dist/assets/psyche-patterns-page-BkRiNpI_.js +5 -0
  58. package/dist/assets/psyche-questionnaire-builder-page-Du-7HwPC.js +1 -0
  59. package/dist/assets/psyche-questionnaire-detail-page-C5-yf8MF.js +1 -0
  60. package/dist/assets/psyche-questionnaire-run-detail-page-CoSH5O6R.js +1 -0
  61. package/dist/assets/psyche-questionnaire-run-page-ChI7c1wy.js +1 -0
  62. package/dist/assets/psyche-questionnaires-page-xUcJJzbI.js +1 -0
  63. package/dist/assets/psyche-report-detail-page-BPrZCFZS.js +3 -0
  64. package/dist/assets/psyche-reports-page-D2EXJmm6.js +1 -0
  65. package/dist/assets/{psyche-schemas-HFmg37Wj.js → psyche-schemas-RcZYaokx.js} +1 -1
  66. package/dist/assets/psyche-schemas-beliefs-page-h0ooZ1lp.js +9 -0
  67. package/dist/assets/psyche-screen-time-page-_3-4LikV.js +1 -0
  68. package/dist/assets/psyche-self-observation-page-BdbCsgR5.js +1 -0
  69. package/dist/assets/psyche-values-page-DuUVki5e.js +5 -0
  70. package/dist/assets/report-chain-fields-BBMz0sGI.js +1 -0
  71. package/dist/assets/{rewards-page-C2HQjIAf.js → rewards-page-PZFPa6rR.js} +1 -1
  72. package/dist/assets/{scheduling-rules-editor-BHOpHOrV.js → scheduling-rules-editor-B9KNKsQz.js} +1 -1
  73. package/dist/assets/schema-badge-D93RcG36.js +1 -0
  74. package/dist/assets/schema-visuals-CvC9a3i6.js +1 -0
  75. package/dist/assets/select-menu-Cdq7foRu.js +1 -0
  76. package/dist/assets/{settings-agents-page-VuYXTiyc.js → settings-agents-page-DPx2F6wf.js} +3 -3
  77. package/dist/assets/{settings-bin-page-BNzvYaOk.js → settings-bin-page-COwP3gjo.js} +1 -1
  78. package/dist/assets/settings-calendar-page-C-ghE0YT.js +5 -0
  79. package/dist/assets/{settings-data-page-CGSlryuI.js → settings-data-page-CBubzKBw.js} +1 -1
  80. package/dist/assets/{settings-logs-page-BTK5fine.js → settings-logs-page-uOuXMeBm.js} +1 -1
  81. package/dist/assets/{settings-mobile-page-CRaObOGo.js → settings-mobile-page-C5rfj8_r.js} +1 -1
  82. package/dist/assets/settings-models-page-DijmUWdU.js +1 -0
  83. package/dist/assets/{settings-page-BP81Mb5R.js → settings-page-B4u5TR5g.js} +1 -1
  84. package/dist/assets/{settings-rewards-page-CDJ1PH2G.js → settings-rewards-page-COiTwkMH.js} +1 -1
  85. package/dist/assets/{settings-section-nav-CCFm27r2.js → settings-section-nav-Dc4IeVBt.js} +1 -1
  86. package/dist/assets/{settings-users-page-TdUocFPa.js → settings-users-page-DEGa5DNn.js} +1 -1
  87. package/dist/assets/settings-wiki-page-Cvsiz5_e.js +1 -0
  88. package/dist/assets/{sleep-page-cI1GMVzk.js → sleep-page-BnSOwkEU.js} +1 -1
  89. package/dist/assets/{sports-page-06LTqp0V.js → sports-page-l1RqXzA_.js} +1 -1
  90. package/dist/assets/{state-B-4sS1xO.js → state-VYvD1QVP.js} +1 -1
  91. package/dist/assets/{strategies-page-DXP9Kx8s.js → strategies-page-DEnPlpAs.js} +1 -1
  92. package/dist/assets/{strategy-detail-page-D6mx_Mik.js → strategy-detail-page-Ls8bxKeH.js} +1 -1
  93. package/dist/assets/strategy-dialog-DMyRKrWf.js +1 -0
  94. package/dist/assets/surface-Bfz_sLX6.js +1 -0
  95. package/dist/assets/{table-WfAPUppN.js → table-C0VTeqw0.js} +1 -1
  96. package/dist/assets/task-detail-page-CgrYgQLD.js +1 -0
  97. package/dist/assets/{timebox-planning-dialog-CaCnoslG.js → timebox-planning-dialog-Ww0NGLLo.js} +1 -1
  98. package/dist/assets/today-page-CIuFHMi1.js +1 -0
  99. package/dist/assets/training-load-page-BIwc648i.js +1 -0
  100. package/dist/assets/{ui-C13Nbgas.js → ui-CsEkP2V8.js} +4 -4
  101. package/dist/assets/use-psyche-focus-target-qxT5Oy_z.js +1 -0
  102. package/dist/assets/{vendor-DHkYh85p.js → vendor-kIz9EZnX.js} +237 -222
  103. package/dist/assets/{vitals-page-BQvEjTc6.js → vitals-page-Dz1Jt5H8.js} +1 -1
  104. package/dist/assets/{weekly-review-page-Tp6Q9CRj.js → weekly-review-page-BFpBe1kI.js} +1 -1
  105. package/dist/assets/weight-loss-page-BgMoBpBt.js +5 -0
  106. package/dist/assets/{wiki-article-markdown-DQYohmW2.js → wiki-article-markdown-gsPTXTg1.js} +1 -1
  107. package/dist/assets/{wiki-editor-page-Dem_3eZv.js → wiki-editor-page-BpAZHooY.js} +7 -7
  108. package/dist/assets/{wiki-ingest-history-page-BxoOcCoJ.js → wiki-ingest-history-page-C-ig8O22.js} +1 -1
  109. package/dist/assets/{wiki-ingest-modal-DhguKk3J.js → wiki-ingest-modal-BK4eQgqs.js} +1 -1
  110. package/dist/assets/{wiki-page-BLRxVXkl.js → wiki-page-CMTZ60Zt.js} +1 -1
  111. package/dist/assets/workbench-flow-page-BIpWUcLJ.js +5 -0
  112. package/dist/assets/workbench-page-CtCjYSRe.js +1 -0
  113. package/dist/assets/workout-detail-page-DD9IGN6l.js +2 -0
  114. package/dist/index.html +9 -9
  115. package/dist/openclaw/local-runtime.js +41 -14
  116. package/dist/server/server/migrations/067_weight_loss_daily_active_overrides.sql +13 -0
  117. package/dist/server/server/src/app.js +103 -30
  118. package/dist/server/server/src/health-weight-loss.js +457 -55
  119. package/dist/server/server/src/health.js +12 -4
  120. package/dist/server/server/src/movement.js +84 -1
  121. package/dist/server/server/src/openapi.js +123 -18
  122. package/dist/server/server/src/repositories/model-settings.js +12 -9
  123. package/dist/server/server/src/repositories/settings.js +19 -5
  124. package/dist/server/server/src/watch-mobile.js +22 -10
  125. package/dist/server/src/components/ui/info-tooltip.js +6 -6
  126. package/dist/server/src/lib/api.js +14 -4
  127. package/dist/server/src/lib/theme-system.js +8 -0
  128. package/openclaw.plugin.json +1 -1
  129. package/package.json +3 -3
  130. package/server/migrations/067_weight_loss_daily_active_overrides.sql +13 -0
  131. package/skills/forge-openclaw/SKILL.md +13 -0
  132. package/skills/forge-openclaw/entity_conversation_playbooks.md +7 -1
  133. package/dist/assets/ai-surface-workspace-qgk_B57-.js +0 -1
  134. package/dist/assets/atlas-panel-rfH2qOez.js +0 -1
  135. package/dist/assets/calendar-week-toolbar-DU1Q4RYj.js +0 -1
  136. package/dist/assets/daily-metrics-dashboard-LjuGAB3f.js +0 -1
  137. package/dist/assets/entity-notes-surface-DgEgicaE.js +0 -1
  138. package/dist/assets/faceted-token-search-CE1YauRd.js +0 -1
  139. package/dist/assets/flagship-signal-deck-DDds90Gl.js +0 -1
  140. package/dist/assets/goals-page-f_39hvUV.js +0 -1
  141. package/dist/assets/index-BHTUu_4M.js +0 -19
  142. package/dist/assets/index-CZbuZQjw.css +0 -1
  143. package/dist/assets/insight-flow-dialog-pzAzyayN.js +0 -1
  144. package/dist/assets/knowledge-graph-page-UQ3skqEi.js +0 -1
  145. package/dist/assets/metric-tile-DX6TclqM.js +0 -1
  146. package/dist/assets/movement-page-6HP6nGJx.js +0 -1
  147. package/dist/assets/note-markdown-DiW2-5d3.js +0 -3
  148. package/dist/assets/note-tags-input-DDLXf54U.js +0 -1
  149. package/dist/assets/notes-page-BuguDjhz.js +0 -1
  150. package/dist/assets/orbit-map-GD05-0oS.js +0 -1
  151. package/dist/assets/overview-page-DuOs2OCB.js +0 -1
  152. package/dist/assets/page-hero-CQWo1Mm_.js +0 -1
  153. package/dist/assets/pill-cluster-BJogDRDJ.js +0 -1
  154. package/dist/assets/preferences-page-BaJTMU1I.js +0 -1
  155. package/dist/assets/psyche-behaviors-page-Dmm_Io9D.js +0 -5
  156. package/dist/assets/psyche-flashcards-page-BgNKJ6QJ.js +0 -1
  157. package/dist/assets/psyche-goal-map-page-DXJs98Vr.js +0 -1
  158. package/dist/assets/psyche-graph-CFgs_Bqc.js +0 -1
  159. package/dist/assets/psyche-mode-guide-page-XPgRfCOf.js +0 -1
  160. package/dist/assets/psyche-modes-page-B-GA8oRF.js +0 -1
  161. package/dist/assets/psyche-page--r6a3e1t.js +0 -1
  162. package/dist/assets/psyche-patterns-page-BM5-3bMm.js +0 -5
  163. package/dist/assets/psyche-questionnaire-builder-page-CJshQ-mg.js +0 -1
  164. package/dist/assets/psyche-questionnaire-detail-page-USmR5G5A.js +0 -1
  165. package/dist/assets/psyche-questionnaire-run-detail-page-D7iBCmTi.js +0 -1
  166. package/dist/assets/psyche-questionnaire-run-page-Cpil-kDh.js +0 -1
  167. package/dist/assets/psyche-questionnaires-page-C-_y3VwS.js +0 -1
  168. package/dist/assets/psyche-report-detail-page--dkSPRaj.js +0 -3
  169. package/dist/assets/psyche-reports-page-CUaOXmIN.js +0 -1
  170. package/dist/assets/psyche-schemas-beliefs-page-BX6xaap3.js +0 -9
  171. package/dist/assets/psyche-screen-time-page-CAAI4mD7.js +0 -1
  172. package/dist/assets/psyche-self-observation-page-BZ6FLuwa.js +0 -1
  173. package/dist/assets/psyche-values-page-yEV6MGt8.js +0 -5
  174. package/dist/assets/report-chain-fields-fZ8Xd4H6.js +0 -1
  175. package/dist/assets/schema-badge-DyKbxb51.js +0 -1
  176. package/dist/assets/schema-visuals-D6nxjbYC.js +0 -1
  177. package/dist/assets/select-menu-BX-pZNqL.js +0 -1
  178. package/dist/assets/settings-calendar-page-CjSFB53S.js +0 -5
  179. package/dist/assets/settings-models-page-DFshpYF8.js +0 -1
  180. package/dist/assets/settings-wiki-page-B2zX0QQG.js +0 -1
  181. package/dist/assets/strategy-dialog-BvzomTaF.js +0 -1
  182. package/dist/assets/task-detail-page-BIWIggdp.js +0 -1
  183. package/dist/assets/today-page-DO2mRPT2.js +0 -1
  184. package/dist/assets/training-load-page-CyZ0mlEr.js +0 -1
  185. package/dist/assets/use-psyche-focus-target-C1C_XjYG.js +0 -1
  186. package/dist/assets/weight-loss-page-BBzlhLVV.js +0 -1
  187. package/dist/assets/workbench-flow-page-DqMkCCTy.js +0 -5
  188. package/dist/assets/workbench-page-BWd02wPw.js +0 -1
  189. package/dist/assets/workout-detail-page-BD8u7GyL.js +0 -2
@@ -2777,6 +2777,7 @@ function expectedWorkoutEvidenceCounts(derived) {
2777
2777
  expectedTimeSeriesSamples,
2778
2778
  expectedHeartRateSamples,
2779
2779
  expectedRoutePoints,
2780
+ rawEvidenceVersion,
2780
2781
  hasCurrentRawEvidenceVersion: rawEvidenceVersion === CURRENT_WORKOUT_RAW_EVIDENCE_VERSION,
2781
2782
  hasEvidenceMetadata: syncTimeSeriesCount !== null ||
2782
2783
  captureHeartRateCount !== null ||
@@ -2819,7 +2820,9 @@ function mobileHealthWorkoutImportState(userId) {
2819
2820
  ORDER BY w.started_at DESC`)
2820
2821
  .all(userId);
2821
2822
  const alreadyUploadedWorkoutExternalUids = [];
2823
+ const incompleteWorkoutExternalUids = [];
2822
2824
  let incompleteWorkoutCount = 0;
2825
+ let staleEvidenceVersionWorkoutCount = 0;
2823
2826
  let timeSeriesSampleCount = 0;
2824
2827
  let heartRateSampleCount = 0;
2825
2828
  let routePointCount = 0;
@@ -2829,27 +2832,32 @@ function mobileHealthWorkoutImportState(userId) {
2829
2832
  const actualTimeSeriesCount = Math.max(0, row.time_series_count ?? 0);
2830
2833
  const actualHeartRateCount = Math.max(0, row.heart_rate_count ?? 0);
2831
2834
  const actualRoutePointCount = Math.max(0, row.route_point_count ?? 0);
2832
- const evidenceComplete = evidenceCounts.hasEvidenceMetadata
2833
- ? evidenceCounts.hasCurrentRawEvidenceVersion &&
2834
- actualTimeSeriesCount >= evidenceCounts.expectedTimeSeriesSamples &&
2835
+ const evidenceCountsComplete = evidenceCounts.hasEvidenceMetadata
2836
+ ? actualTimeSeriesCount >= evidenceCounts.expectedTimeSeriesSamples &&
2835
2837
  actualHeartRateCount >= evidenceCounts.expectedHeartRateSamples &&
2836
2838
  actualRoutePointCount >= evidenceCounts.expectedRoutePoints
2837
2839
  : false;
2838
- if (evidenceComplete) {
2840
+ if (evidenceCountsComplete) {
2839
2841
  alreadyUploadedWorkoutExternalUids.push(row.external_uid.toLowerCase());
2840
2842
  timeSeriesSampleCount += actualTimeSeriesCount;
2841
2843
  heartRateSampleCount += actualHeartRateCount;
2842
2844
  routePointCount += actualRoutePointCount;
2845
+ if (!evidenceCounts.hasCurrentRawEvidenceVersion) {
2846
+ staleEvidenceVersionWorkoutCount += 1;
2847
+ }
2843
2848
  }
2844
2849
  else {
2845
2850
  incompleteWorkoutCount += 1;
2851
+ incompleteWorkoutExternalUids.push(row.external_uid.toLowerCase());
2846
2852
  }
2847
2853
  }
2848
2854
  return {
2849
2855
  alreadyUploadedWorkoutExternalUids,
2856
+ incompleteWorkoutExternalUids,
2850
2857
  alreadyUploadedWorkoutCount: alreadyUploadedWorkoutExternalUids.length,
2851
2858
  existingWorkoutCount: rows.length,
2852
2859
  incompleteWorkoutCount,
2860
+ staleEvidenceVersionWorkoutCount,
2853
2861
  timeSeriesSampleCount,
2854
2862
  heartRateSampleCount,
2855
2863
  routePointCount,
@@ -905,6 +905,69 @@ function decodeMovementTimelineCursor(rawValue) {
905
905
  return null;
906
906
  }
907
907
  }
908
+ function metricNumber(metrics, key) {
909
+ const metric = metrics[key];
910
+ if (!metric || typeof metric !== "object") {
911
+ return null;
912
+ }
913
+ const record = metric;
914
+ for (const field of ["latest", "average", "total", "maximum"]) {
915
+ const value = record[field];
916
+ if (typeof value === "number" && Number.isFinite(value)) {
917
+ return value;
918
+ }
919
+ }
920
+ return null;
921
+ }
922
+ function latestKnownBodyMassKg(userId) {
923
+ const nutritionRow = getDatabase()
924
+ .prepare(`SELECT weight_kg
925
+ FROM nutrition_body_checkins
926
+ WHERE user_id = ?
927
+ AND weight_kg IS NOT NULL
928
+ ORDER BY checked_at DESC
929
+ LIMIT 1`)
930
+ .get(userId);
931
+ if (nutritionRow?.weight_kg && nutritionRow.weight_kg > 0) {
932
+ return nutritionRow.weight_kg;
933
+ }
934
+ const summaryRows = getDatabase()
935
+ .prepare(`SELECT metrics_json
936
+ FROM health_daily_summaries
937
+ WHERE user_id = ?
938
+ AND summary_type = 'vitals'
939
+ ORDER BY date_key DESC, updated_at DESC
940
+ LIMIT 14`)
941
+ .all(userId);
942
+ for (const row of summaryRows) {
943
+ const metrics = safeJsonParse(row.metrics_json, {});
944
+ const bodyMass = metricNumber(metrics, "bodyMass");
945
+ if (bodyMass != null && bodyMass > 0) {
946
+ return bodyMass;
947
+ }
948
+ }
949
+ return 80;
950
+ }
951
+ function estimateMovementActiveCalories(input) {
952
+ const met = input.expectedMet;
953
+ if (met == null || met <= 1) {
954
+ return null;
955
+ }
956
+ const seconds = input.movingSeconds > 0
957
+ ? input.movingSeconds
958
+ : durationSeconds(input.startedAt, input.endedAt);
959
+ if (seconds <= 0) {
960
+ return null;
961
+ }
962
+ const bodyMassKg = latestKnownBodyMassKg(input.userId);
963
+ const activeMet = Math.max(0, met - 1);
964
+ const minutes = seconds / 60;
965
+ return {
966
+ caloriesKcal: round((activeMet * 3.5 * bodyMassKg * minutes) / 200, 0),
967
+ bodyMassKg,
968
+ model: "active_met_minus_one_v1"
969
+ };
970
+ }
908
971
  function haversineDistanceMeters(left, right) {
909
972
  const toRadians = (degrees) => (degrees * Math.PI) / 180;
910
973
  const earthRadius = 6_371_000;
@@ -1912,6 +1975,26 @@ function upsertMovementTrip(pairing, settings, input) {
1912
1975
  });
1913
1976
  const effectiveExpectedMet = parsed.expectedMet ??
1914
1977
  inferExpectedMet(parsed.activityType, derivedMetrics.averageSpeedMps ?? parsed.averageSpeedMps);
1978
+ const estimatedCalories = parsed.caloriesKcal == null
1979
+ ? estimateMovementActiveCalories({
1980
+ userId: pairing.user_id,
1981
+ expectedMet: effectiveExpectedMet,
1982
+ movingSeconds: derivedMetrics.movingSeconds,
1983
+ startedAt: derivedMetrics.startedAt,
1984
+ endedAt: derivedMetrics.endedAt
1985
+ })
1986
+ : null;
1987
+ const effectiveCaloriesKcal = parsed.caloriesKcal ?? estimatedCalories?.caloriesKcal ?? null;
1988
+ const metadata = {
1989
+ ...parsed.metadata,
1990
+ ...(estimatedCalories
1991
+ ? {
1992
+ calorieEstimateModel: estimatedCalories.model,
1993
+ calorieEstimateBodyMassKg: estimatedCalories.bodyMassKg,
1994
+ calorieEstimateSource: "forge_server_ingestion"
1995
+ }
1996
+ : {})
1997
+ };
1915
1998
  const id = existing?.id ?? `mtr_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
1916
1999
  getDatabase()
1917
2000
  .prepare(`INSERT INTO movement_trips (
@@ -1946,7 +2029,7 @@ function upsertMovementTrip(pairing, settings, input) {
1946
2029
  linked_people_json = excluded.linked_people_json,
1947
2030
  metadata_json = excluded.metadata_json,
1948
2031
  updated_at = excluded.updated_at`)
1949
- .run(id, parsed.externalUid, pairing.id, pairing.user_id, startPlace?.id ?? null, endPlace?.id ?? null, parsed.label, parsed.status, parsed.travelMode, parsed.activityType, derivedMetrics.startedAt, derivedMetrics.endedAt, derivedMetrics.distanceMeters, derivedMetrics.movingSeconds, derivedMetrics.idleSeconds, derivedMetrics.averageSpeedMps, derivedMetrics.maxSpeedMps, parsed.caloriesKcal, effectiveExpectedMet, JSON.stringify({}), JSON.stringify(uniqStrings(parsed.tags)), JSON.stringify(parsed.linkedEntities), JSON.stringify(parsed.linkedPeople), JSON.stringify(parsed.metadata), existing?.published_note_id ?? null, existing?.created_at ?? now, now);
2032
+ .run(id, parsed.externalUid, pairing.id, pairing.user_id, startPlace?.id ?? null, endPlace?.id ?? null, parsed.label, parsed.status, parsed.travelMode, parsed.activityType, derivedMetrics.startedAt, derivedMetrics.endedAt, derivedMetrics.distanceMeters, derivedMetrics.movingSeconds, derivedMetrics.idleSeconds, derivedMetrics.averageSpeedMps, derivedMetrics.maxSpeedMps, effectiveCaloriesKcal, effectiveExpectedMet, JSON.stringify({}), JSON.stringify(uniqStrings(parsed.tags)), JSON.stringify(parsed.linkedEntities), JSON.stringify(parsed.linkedPeople), JSON.stringify(metadata), existing?.published_note_id ?? null, existing?.created_at ?? now, now);
1950
2033
  reconcileMovementOverlapValidation(pairing.user_id);
1951
2034
  const fresh = getDatabase()
1952
2035
  .prepare(`SELECT * FROM movement_trips WHERE user_id = ? AND external_uid = ?`)
@@ -30,16 +30,14 @@ const HTTP_METHODS = new Set([
30
30
  ]);
31
31
  const mobileHealthSyncProgressSchema = {
32
32
  type: "object",
33
- required: [
34
- "chunkCount",
35
- "receivedBytes",
36
- "receivedCounts",
37
- "byteTotals"
38
- ],
33
+ required: ["chunkCount", "receivedBytes", "receivedCounts", "byteTotals"],
39
34
  properties: {
40
35
  chunkCount: { type: "number" },
41
36
  receivedBytes: { type: "number" },
42
- receivedCounts: { type: "object", additionalProperties: { type: "number" } },
37
+ receivedCounts: {
38
+ type: "object",
39
+ additionalProperties: { type: "number" }
40
+ },
43
41
  byteTotals: { type: "object", additionalProperties: { type: "number" } }
44
42
  }
45
43
  };
@@ -3054,8 +3052,12 @@ export function buildOpenApiDocument() {
3054
3052
  newestUnlock: nullable({
3055
3053
  $ref: "#/components/schemas/GamificationCatalogEntry"
3056
3054
  }),
3057
- nextTargets: arrayOf({ $ref: "#/components/schemas/GamificationCatalogEntry" }),
3058
- recentlyUnlocked: arrayOf({ $ref: "#/components/schemas/GamificationCatalogEntry" })
3055
+ nextTargets: arrayOf({
3056
+ $ref: "#/components/schemas/GamificationCatalogEntry"
3057
+ }),
3058
+ recentlyUnlocked: arrayOf({
3059
+ $ref: "#/components/schemas/GamificationCatalogEntry"
3060
+ })
3059
3061
  }
3060
3062
  };
3061
3063
  const xpMetricsPayload = {
@@ -3100,7 +3102,9 @@ export function buildOpenApiDocument() {
3100
3102
  newestUnlock: nullable({
3101
3103
  $ref: "#/components/schemas/GamificationCatalogEntry"
3102
3104
  }),
3103
- nextTargets: arrayOf({ $ref: "#/components/schemas/GamificationCatalogEntry" }),
3105
+ nextTargets: arrayOf({
3106
+ $ref: "#/components/schemas/GamificationCatalogEntry"
3107
+ }),
3104
3108
  equipment: { $ref: "#/components/schemas/GamificationEquipment" },
3105
3109
  mascot: { $ref: "#/components/schemas/GamificationMascotState" },
3106
3110
  celebrations: arrayOf({
@@ -3652,6 +3656,7 @@ export function buildOpenApiDocument() {
3652
3656
  "movement",
3653
3657
  "lifeForce",
3654
3658
  "workbench",
3659
+ "weightLoss",
3655
3660
  "psyche"
3656
3661
  ],
3657
3662
  properties: {
@@ -3674,6 +3679,7 @@ export function buildOpenApiDocument() {
3674
3679
  movement: { type: "string" },
3675
3680
  lifeForce: { type: "string" },
3676
3681
  workbench: { type: "string" },
3682
+ weightLoss: { type: "string" },
3677
3683
  psyche: { type: "string" }
3678
3684
  }
3679
3685
  },
@@ -3967,6 +3973,22 @@ export function buildOpenApiDocument() {
3967
3973
  "weeklyReview",
3968
3974
  "sleepOverview",
3969
3975
  "sportsOverview",
3976
+ "trainingLoad",
3977
+ "weightLoss",
3978
+ "weightLossTarget",
3979
+ "weightLossDailyActiveCalories",
3980
+ "weightLossFoodsSearch",
3981
+ "weightLossFoodsBarcode",
3982
+ "weightLossFoodLogs",
3983
+ "weightLossFoodLogDetail",
3984
+ "weightLossParse",
3985
+ "weightLossBodyCheckins",
3986
+ "weightLossAppearanceCheckins",
3987
+ "weightLossSubjectiveCheckins",
3988
+ "weightLossGutCheckins",
3989
+ "weightLossPatterns",
3990
+ "weightLossExperiments",
3991
+ "weightLossExperimentDetail",
3970
3992
  "lifeForce",
3971
3993
  "lifeForceProfile",
3972
3994
  "lifeForceWeekdayTemplate",
@@ -4015,6 +4037,22 @@ export function buildOpenApiDocument() {
4015
4037
  weeklyReview: { type: "string" },
4016
4038
  sleepOverview: { type: "string" },
4017
4039
  sportsOverview: { type: "string" },
4040
+ trainingLoad: { type: "string" },
4041
+ weightLoss: { type: "string" },
4042
+ weightLossTarget: { type: "string" },
4043
+ weightLossDailyActiveCalories: { type: "string" },
4044
+ weightLossFoodsSearch: { type: "string" },
4045
+ weightLossFoodsBarcode: { type: "string" },
4046
+ weightLossFoodLogs: { type: "string" },
4047
+ weightLossFoodLogDetail: { type: "string" },
4048
+ weightLossParse: { type: "string" },
4049
+ weightLossBodyCheckins: { type: "string" },
4050
+ weightLossAppearanceCheckins: { type: "string" },
4051
+ weightLossSubjectiveCheckins: { type: "string" },
4052
+ weightLossGutCheckins: { type: "string" },
4053
+ weightLossPatterns: { type: "string" },
4054
+ weightLossExperiments: { type: "string" },
4055
+ weightLossExperimentDetail: { type: "string" },
4018
4056
  lifeForce: { type: "string" },
4019
4057
  lifeForceProfile: { type: "string" },
4020
4058
  lifeForceWeekdayTemplate: { type: "string" },
@@ -4786,9 +4824,16 @@ export function buildOpenApiDocument() {
4786
4824
  sync: {
4787
4825
  type: "object",
4788
4826
  additionalProperties: false,
4789
- required: ["fullSyncCompletedAt", "lastDailySyncAt", "lastSyncedDateKey"],
4827
+ required: [
4828
+ "fullSyncCompletedAt",
4829
+ "lastDailySyncAt",
4830
+ "lastSyncedDateKey"
4831
+ ],
4790
4832
  properties: {
4791
- fullSyncCompletedAt: nullable({ type: "string", format: "date-time" }),
4833
+ fullSyncCompletedAt: nullable({
4834
+ type: "string",
4835
+ format: "date-time"
4836
+ }),
4792
4837
  lastDailySyncAt: nullable({ type: "string", format: "date-time" }),
4793
4838
  lastSyncedDateKey: nullable({ type: "string" })
4794
4839
  }
@@ -4925,10 +4970,20 @@ export function buildOpenApiDocument() {
4925
4970
  sync: {
4926
4971
  type: "object",
4927
4972
  additionalProperties: false,
4928
- required: ["fullSyncCompletedAt", "lastDailySyncAt", "lastSyncedDateKey"],
4973
+ required: [
4974
+ "fullSyncCompletedAt",
4975
+ "lastDailySyncAt",
4976
+ "lastSyncedDateKey"
4977
+ ],
4929
4978
  properties: {
4930
- fullSyncCompletedAt: nullable({ type: "string", format: "date-time" }),
4931
- lastDailySyncAt: nullable({ type: "string", format: "date-time" }),
4979
+ fullSyncCompletedAt: nullable({
4980
+ type: "string",
4981
+ format: "date-time"
4982
+ }),
4983
+ lastDailySyncAt: nullable({
4984
+ type: "string",
4985
+ format: "date-time"
4986
+ }),
4932
4987
  lastSyncedDateKey: nullable({ type: "string" })
4933
4988
  }
4934
4989
  }
@@ -5218,7 +5273,10 @@ export function buildOpenApiDocument() {
5218
5273
  }
5219
5274
  },
5220
5275
  trainingIntelligence: { type: "object", additionalProperties: true },
5221
- activityBreakdown: arrayOf({ type: "object", additionalProperties: true }),
5276
+ activityBreakdown: arrayOf({
5277
+ type: "object",
5278
+ additionalProperties: true
5279
+ }),
5222
5280
  vitalsTrend: arrayOf({ type: "object", additionalProperties: true }),
5223
5281
  sessionSignals: arrayOf({ type: "object", additionalProperties: true }),
5224
5282
  targetModel: { type: "object", additionalProperties: true }
@@ -5345,7 +5403,10 @@ export function buildOpenApiDocument() {
5345
5403
  todayLedger: { type: "object", additionalProperties: true },
5346
5404
  recentMeals: arrayOf(nutritionFoodLog),
5347
5405
  bodyCheckins: arrayOf({ type: "object", additionalProperties: true }),
5348
- appearanceCheckins: arrayOf({ type: "object", additionalProperties: true }),
5406
+ appearanceCheckins: arrayOf({
5407
+ type: "object",
5408
+ additionalProperties: true
5409
+ }),
5349
5410
  energyModel: { type: "object", additionalProperties: true },
5350
5411
  weightTrend: { type: "object", additionalProperties: true },
5351
5412
  foodQuality: { type: "object", additionalProperties: true },
@@ -5820,6 +5881,47 @@ export function buildOpenApiDocument() {
5820
5881
  }
5821
5882
  }
5822
5883
  },
5884
+ "/api/v1/health/weight-loss/daily-active-calories": {
5885
+ patch: {
5886
+ tags: ["Health"],
5887
+ summary: "Set or clear the user-edited active calorie allowance for one weight-loss day",
5888
+ requestBody: {
5889
+ content: {
5890
+ "application/json": {
5891
+ schema: {
5892
+ type: "object",
5893
+ additionalProperties: false,
5894
+ properties: {
5895
+ userId: { type: "string" },
5896
+ dayKey: {
5897
+ type: "string",
5898
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$"
5899
+ },
5900
+ activeCaloriesKcal: {
5901
+ type: ["number", "null"],
5902
+ minimum: 0
5903
+ },
5904
+ notes: { type: "string" }
5905
+ }
5906
+ }
5907
+ }
5908
+ }
5909
+ },
5910
+ responses: {
5911
+ "200": jsonResponse({
5912
+ type: "object",
5913
+ required: ["dayKey"],
5914
+ properties: {
5915
+ dayKey: { type: "string" },
5916
+ override: {
5917
+ type: ["object", "null"],
5918
+ additionalProperties: true
5919
+ }
5920
+ }
5921
+ }, "Updated daily active calorie override")
5922
+ }
5923
+ }
5924
+ },
5823
5925
  "/api/v1/health/weight-loss/foods/search": {
5824
5926
  post: {
5825
5927
  tags: ["Health"],
@@ -8865,7 +8967,10 @@ export function buildOpenApiDocument() {
8865
8967
  "connectionHelp"
8866
8968
  ],
8867
8969
  properties: {
8868
- provider: { type: "string", enum: CALENDAR_PROVIDER_VALUES },
8970
+ provider: {
8971
+ type: "string",
8972
+ enum: CALENDAR_PROVIDER_VALUES
8973
+ },
8869
8974
  label: { type: "string" },
8870
8975
  supportsDedicatedForgeCalendar: { type: "boolean" },
8871
8976
  connectionHelp: { type: "string" }
@@ -203,7 +203,12 @@ export function syncForgeManagedWikiProfile(secrets) {
203
203
  .get();
204
204
  const connectionId = settings?.forge_wiki_connection_id?.trim() ?? "";
205
205
  const fallbackModel = settings?.forge_wiki_model?.trim() || "gpt-5.4-mini";
206
- const connection = connectionId ? getAiModelConnectionById(connectionId) : null;
206
+ const requestedConnection = connectionId
207
+ ? getAiModelConnectionById(connectionId)
208
+ : null;
209
+ const connection = requestedConnection?.provider === "openai-codex"
210
+ ? requestedConnection
211
+ : null;
207
212
  const row = connectionId
208
213
  ? getDatabase()
209
214
  .prepare(`SELECT secret_id
@@ -214,18 +219,16 @@ export function syncForgeManagedWikiProfile(secrets) {
214
219
  upsertWikiLlmProfile({
215
220
  id: FORGE_MANAGED_WIKI_PROFILE_ID,
216
221
  label: "Forge wiki ingest",
217
- provider: connection?.provider === "openai-compatible"
218
- ? "openai-compatible"
219
- : connection?.provider === "openai-codex"
220
- ? "openai-codex"
221
- : "openai-responses",
222
- baseUrl: connection?.baseUrl ?? DEFAULT_OPENAI_BASE_URL,
222
+ provider: "openai-codex",
223
+ baseUrl: connection?.baseUrl ?? DEFAULT_OPENAI_CODEX_BASE_URL,
223
224
  model: connection?.model ?? fallbackModel,
224
- secretId: row?.secret_id ?? null,
225
+ secretId: connection ? (row?.secret_id ?? null) : null,
225
226
  enabled: true,
226
227
  metadata: {
227
228
  managedBySettings: true,
228
- connectionId: connection?.id ?? null
229
+ connectionId: connection?.id ?? null,
230
+ authMode: "oauth",
231
+ billing: "chatgpt-codex-oauth"
229
232
  }
230
233
  }, secrets);
231
234
  }
@@ -6,7 +6,7 @@ import { logForgeDebug } from "../debug.js";
6
6
  import { recordActivityEvent } from "./activity-events.js";
7
7
  import { recordEventLog } from "./event-log.js";
8
8
  import { resolveGoogleCalendarOauthPublicConfig } from "../services/google-calendar-oauth-config.js";
9
- import { buildConnectionAgentIdentity, FORGE_DEFAULT_AGENT_ID, listAiModelConnections, syncForgeManagedWikiProfile } from "./model-settings.js";
9
+ import { buildConnectionAgentIdentity, FORGE_DEFAULT_AGENT_ID, getAiModelConnectionById, listAiModelConnections, syncForgeManagedWikiProfile } from "./model-settings.js";
10
10
  import { listUsersByIds } from "./users.js";
11
11
  import { agentBootstrapPolicySchema, agentScopePolicySchema, createAgentTokenSchema, legacyAgentBootstrapPolicy, defaultAgentScopePolicy, agentIdentitySchema, customThemeSchema, settingsPayloadSchema, updateSettingsSchema } from "../types.js";
12
12
  const settingsFileSchema = settingsPayloadSchema.deepPartial();
@@ -591,7 +591,10 @@ function buildSettingsPayloadFromDatabase() {
591
591
  const basicChatConnectionId = normalizeModelConnectionId(row.forge_basic_chat_connection_id);
592
592
  const wikiConnectionId = normalizeModelConnectionId(row.forge_wiki_connection_id);
593
593
  const basicChatConnection = connections.find((entry) => entry.id === basicChatConnectionId) ?? null;
594
- const wikiConnection = connections.find((entry) => entry.id === wikiConnectionId) ?? null;
594
+ const wikiConnectionCandidate = connections.find((entry) => entry.id === wikiConnectionId) ?? null;
595
+ const wikiConnection = wikiConnectionCandidate?.provider === "openai-codex"
596
+ ? wikiConnectionCandidate
597
+ : null;
595
598
  const customTheme = parseCustomThemeJson(row.custom_theme_json);
596
599
  return settingsPayloadSchema.parse({
597
600
  profile: {
@@ -832,9 +835,20 @@ function updateSettingsInternal(input, options = {}) {
832
835
  current.modelSettings.forgeAgent.basicChat.model
833
836
  },
834
837
  wiki: {
835
- connectionId: parsed.modelSettings?.forgeAgent?.wiki?.connectionId !== undefined
836
- ? normalizeModelConnectionId(parsed.modelSettings.forgeAgent.wiki.connectionId)
837
- : (current.modelSettings.forgeAgent.wiki.connectionId ?? ""),
838
+ connectionId: (() => {
839
+ const normalized = parsed.modelSettings?.forgeAgent?.wiki?.connectionId !==
840
+ undefined
841
+ ? normalizeModelConnectionId(parsed.modelSettings.forgeAgent.wiki.connectionId)
842
+ : (current.modelSettings.forgeAgent.wiki.connectionId ?? "");
843
+ if (!normalized) {
844
+ return "";
845
+ }
846
+ const connection = getAiModelConnectionById(normalized);
847
+ if (connection?.provider !== "openai-codex") {
848
+ throw new Error("KarpaWiki ingest must use an OpenAI Codex OAuth model connection, not an OpenAI Platform API connection.");
849
+ }
850
+ return normalized;
851
+ })(),
838
852
  model: parsed.modelSettings?.forgeAgent?.wiki?.model?.trim() ||
839
853
  current.modelSettings.forgeAgent.wiki.model
840
854
  }
@@ -911,26 +911,38 @@ const captureCommandPayloadSchema = z.object({
911
911
  linkedContext: watchLinkedContextSchema.default({}),
912
912
  payload: z.record(z.string(), z.unknown()).default({})
913
913
  });
914
+ const booleanLikeSchema = z.preprocess((value) => {
915
+ if (typeof value === "string") {
916
+ const normalized = value.trim().toLowerCase();
917
+ if (normalized === "true") {
918
+ return true;
919
+ }
920
+ if (normalized === "false") {
921
+ return false;
922
+ }
923
+ }
924
+ return value;
925
+ }, z.boolean());
926
+ const nullableNumberLikeSchema = z.preprocess((value) => {
927
+ if (value === "" || value === null || value === undefined) {
928
+ return null;
929
+ }
930
+ return value;
931
+ }, z.coerce.number().int().min(60).max(86_400).nullable());
914
932
  const taskRunStartCommandPayloadSchema = z.object({
915
933
  taskId: z.string().trim().min(1),
916
934
  actor: z.string().trim().min(1).optional(),
917
935
  timerMode: z.enum(["planned", "unlimited"]).default("unlimited"),
918
- plannedDurationSeconds: z
919
- .number()
920
- .int()
921
- .min(60)
922
- .max(86_400)
923
- .nullable()
924
- .default(null),
925
- isCurrent: z.boolean().default(true),
926
- leaseTtlSeconds: z.number().int().min(1).max(14_400).default(900),
936
+ plannedDurationSeconds: nullableNumberLikeSchema.default(null),
937
+ isCurrent: booleanLikeSchema.default(true),
938
+ leaseTtlSeconds: z.coerce.number().int().min(1).max(14_400).default(900),
927
939
  note: z.string().trim().default(""),
928
940
  overrideReason: z.string().trim().optional()
929
941
  });
930
942
  const taskRunIdCommandPayloadSchema = z.object({
931
943
  runId: z.string().trim().min(1),
932
944
  actor: z.string().trim().min(1).optional(),
933
- leaseTtlSeconds: z.number().int().min(1).max(14_400).default(900),
945
+ leaseTtlSeconds: z.coerce.number().int().min(1).max(14_400).default(900),
934
946
  note: z.string().trim().default(""),
935
947
  overrideReason: z.string().trim().optional()
936
948
  });
@@ -8,9 +8,9 @@ const TOOLTIP_MAX_WIDTH_PX = 320;
8
8
  const TOOLTIP_MIN_HEIGHT_BELOW_PX = 180;
9
9
  const TOOLTIP_ESTIMATED_HEIGHT_PX = 220;
10
10
  export function FieldHint({ children, className }) {
11
- return (_jsx("div", { className: cn("text-sm leading-6 text-white/50", className), children: children }));
11
+ return (_jsx("div", { className: cn("text-sm leading-6 text-[var(--ui-ink-soft)]", className), children: children }));
12
12
  }
13
- export function InfoTooltip({ content, title, label = "Explain this field", className, panelClassName }) {
13
+ export function InfoTooltip({ content, title, label = "Explain this field", className, panelClassName, maxWidthPx = TOOLTIP_MAX_WIDTH_PX }) {
14
14
  const [open, setOpen] = useState(false);
15
15
  const [panelStyle, setPanelStyle] = useState({});
16
16
  const containerRef = useRef(null);
@@ -18,7 +18,7 @@ export function InfoTooltip({ content, title, label = "Explain this field", clas
18
18
  const tooltipId = useId();
19
19
  const tooltipPanel = typeof document === "undefined"
20
20
  ? null
21
- : createPortal(_jsxs("span", { id: tooltipId, role: "tooltip", "aria-hidden": !open, "data-state": open ? "open" : "closed", style: panelStyle, className: cn("pointer-events-none fixed z-[9999] grid overflow-y-auto rounded-[8px] border border-white/10 bg-[#0c111e] px-3 py-2.5 text-left font-sans text-sm normal-case leading-6 tracking-normal text-white/74 shadow-[0_18px_48px_rgba(3,8,18,0.42)] transition", open ? "translate-y-0 opacity-100" : "translate-y-1 opacity-0", panelClassName), children: [title ? (_jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.14em] text-white/58", children: title })) : null, _jsx("span", { className: "block min-w-0 whitespace-normal break-words", children: content })] }), document.body);
21
+ : createPortal(_jsxs("span", { id: tooltipId, role: "tooltip", "aria-hidden": !open, "data-state": open ? "open" : "closed", style: panelStyle, className: cn("pointer-events-none fixed z-[9999] grid overflow-y-auto rounded-[8px] border border-[var(--ui-border-subtle)] bg-[var(--surface-glass)] px-3 py-2.5 text-left font-sans text-sm normal-case leading-6 tracking-normal text-[var(--ui-ink-medium)] shadow-[var(--ui-shadow-floating)] backdrop-blur-xl transition", open ? "translate-y-0 opacity-100" : "translate-y-1 opacity-0", panelClassName), children: [title ? (_jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.14em] text-[var(--ui-ink-soft)]", children: title })) : null, _jsx("span", { className: "block min-w-0 whitespace-normal break-words", children: content })] }), document.body);
22
22
  useLayoutEffect(() => {
23
23
  if (!open) {
24
24
  return;
@@ -31,7 +31,7 @@ export function InfoTooltip({ content, title, label = "Explain this field", clas
31
31
  const rect = trigger.getBoundingClientRect();
32
32
  const width = window.innerWidth < 480
33
33
  ? window.innerWidth - TOOLTIP_GUTTER_PX * 2
34
- : Math.min(TOOLTIP_MAX_WIDTH_PX, window.innerWidth - TOOLTIP_GUTTER_PX * 2);
34
+ : Math.min(maxWidthPx, window.innerWidth - TOOLTIP_GUTTER_PX * 2);
35
35
  const centeredLeft = rect.left + rect.width / 2 - width / 2;
36
36
  const maxLeft = window.innerWidth - width - TOOLTIP_GUTTER_PX;
37
37
  const left = Math.max(TOOLTIP_GUTTER_PX, Math.min(centeredLeft, maxLeft));
@@ -53,7 +53,7 @@ export function InfoTooltip({ content, title, label = "Explain this field", clas
53
53
  window.removeEventListener("resize", positionTooltip);
54
54
  window.removeEventListener("scroll", positionTooltip, true);
55
55
  };
56
- }, [open]);
56
+ }, [maxWidthPx, open]);
57
57
  useEffect(() => {
58
58
  if (!open) {
59
59
  return;
@@ -66,7 +66,7 @@ export function InfoTooltip({ content, title, label = "Explain this field", clas
66
66
  document.addEventListener("pointerdown", handlePointerDown);
67
67
  return () => document.removeEventListener("pointerdown", handlePointerDown);
68
68
  }, [open]);
69
- return (_jsxs("span", { ref: containerRef, className: cn("relative inline-flex items-center", className), onMouseEnter: () => setOpen(true), onMouseLeave: () => setOpen(false), children: [_jsx("button", { ref: triggerRef, type: "button", "aria-label": label, "aria-describedby": open ? tooltipId : undefined, "aria-expanded": open, className: "inline-flex size-5 items-center justify-center rounded-full text-white/42 transition hover:bg-white/[0.06] hover:text-white/78 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[rgba(192,193,255,0.35)]", onFocus: () => setOpen(true), onBlur: () => setOpen(false), onClick: () => setOpen((current) => !current), onKeyDown: (event) => {
69
+ return (_jsxs("span", { ref: containerRef, className: cn("relative inline-flex items-center", className), onMouseEnter: () => setOpen(true), onMouseLeave: () => setOpen(false), children: [_jsx("button", { ref: triggerRef, type: "button", "aria-label": label, "aria-describedby": open ? tooltipId : undefined, "aria-expanded": open, className: "inline-flex size-5 items-center justify-center rounded-full text-[var(--ui-ink-faint)] transition hover:bg-[var(--ui-surface-hover)] hover:text-[var(--ui-ink-medium)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--primary)]/35", onFocus: () => setOpen(true), onBlur: () => setOpen(false), onClick: () => setOpen((current) => !current), onKeyDown: (event) => {
70
70
  if (event.key === "Escape") {
71
71
  setOpen(false);
72
72
  }
@@ -740,7 +740,7 @@ export async function listFlashcards(userIds) {
740
740
  {
741
741
  entityTypes: ["flashcard"],
742
742
  userIds: coerceUserIds(userIds),
743
- limit: 500
743
+ limit: 200
744
744
  }
745
745
  ]
746
746
  });
@@ -1771,6 +1771,15 @@ export function updateNutritionTarget(patch, userIds) {
1771
1771
  body: JSON.stringify(patch)
1772
1772
  });
1773
1773
  }
1774
+ export function updateNutritionDailyActiveCalories(patch, userIds) {
1775
+ const search = new URLSearchParams();
1776
+ appendUserIds(search, coerceUserIds(userIds));
1777
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
1778
+ return request(`/api/v1/health/weight-loss/daily-active-calories${suffix}`, {
1779
+ method: "PATCH",
1780
+ body: JSON.stringify(patch)
1781
+ });
1782
+ }
1774
1783
  export function searchNutritionFoods(input) {
1775
1784
  const search = new URLSearchParams();
1776
1785
  appendUserIds(search, coerceUserIds(input.userIds));
@@ -1821,9 +1830,10 @@ export function parseNutritionFoodLogWithChatGpt(input) {
1821
1830
  method: "POST",
1822
1831
  body: JSON.stringify({
1823
1832
  text: input.text,
1824
- imageDescription: input.imageDescription,
1825
- loggedAt: input.loggedAt,
1826
- mealLabel: input.mealLabel
1833
+ mealTime: input.mealTime,
1834
+ imageRefs: input.imageRefs,
1835
+ connectionId: input.connectionId,
1836
+ commitCandidate: input.commitCandidate
1827
1837
  })
1828
1838
  });
1829
1839
  }
@@ -31,6 +31,7 @@ const CUSTOM_THEME_STORAGE_KEYS = [
31
31
  "--secondary",
32
32
  "--tertiary",
33
33
  "--surface",
34
+ "--canvas",
34
35
  "--surface-low",
35
36
  "--surface-panel",
36
37
  "--surface-high",
@@ -41,6 +42,9 @@ const CUSTOM_THEME_STORAGE_KEYS = [
41
42
  "--card-gradient",
42
43
  "--card-shadow",
43
44
  "--card-shadow-hover",
45
+ "--ui-accent",
46
+ "--ui-ink",
47
+ "--ui-ink-muted",
44
48
  "--forge-body-ambient-primary",
45
49
  "--forge-body-ambient-secondary",
46
50
  "--forge-body-gradient-start",
@@ -271,6 +275,7 @@ export function buildForgeThemeVariables(theme) {
271
275
  "--secondary": theme.secondary,
272
276
  "--tertiary": theme.tertiary,
273
277
  "--surface": theme.canvas,
278
+ "--canvas": theme.canvas,
274
279
  "--surface-low": theme.panelLow,
275
280
  "--surface-panel": theme.panel,
276
281
  "--surface-high": theme.panelHigh,
@@ -281,6 +286,9 @@ export function buildForgeThemeVariables(theme) {
281
286
  "--card-gradient": `linear-gradient(180deg, ${alpha(cardTop, 0.96)}, ${alpha(theme.panelLow, 0.94)})`,
282
287
  "--card-shadow": `0 24px 60px ${alpha(theme.canvas, 0.3)}`,
283
288
  "--card-shadow-hover": `0 32px 80px ${alpha(theme.canvas, 0.38)}`,
289
+ "--ui-accent": theme.primary,
290
+ "--ui-ink": theme.ink,
291
+ "--ui-ink-muted": alpha(theme.ink, 0.58),
284
292
  "--forge-body-ambient-primary": alpha(theme.primary, 0.2),
285
293
  "--forge-body-ambient-secondary": alpha(theme.secondary, 0.14),
286
294
  "--forge-body-gradient-start": mixHex(theme.panel, theme.primary, 0.08),