@1agh/maude 0.43.0 → 0.44.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.
Files changed (122) hide show
  1. package/apps/studio/acp/bridge.ts +44 -10
  2. package/apps/studio/acp/env.ts +23 -0
  3. package/apps/studio/acp/login-state.ts +263 -0
  4. package/apps/studio/acp/plugin-bootstrap.ts +31 -25
  5. package/apps/studio/acp/probe.ts +152 -1
  6. package/apps/studio/annotations-layer.tsx +396 -46
  7. package/apps/studio/api.ts +395 -2
  8. package/apps/studio/bin/_audio-search.mjs +140 -0
  9. package/apps/studio/bin/_import-asset-pdf-worker.mjs +34 -0
  10. package/apps/studio/bin/_import-asset.mjs +815 -0
  11. package/apps/studio/bin/_import-brand.mjs +635 -0
  12. package/apps/studio/bin/_import-tokens-alias-resolver.mjs +95 -0
  13. package/apps/studio/bin/_import-tokens.mjs +1201 -0
  14. package/apps/studio/bin/_transcribe.mjs +419 -0
  15. package/apps/studio/bin/_transcribe.test.mjs +80 -0
  16. package/apps/studio/bin/audio-search.sh +28 -0
  17. package/apps/studio/bin/generate.sh +154 -0
  18. package/apps/studio/bin/import-asset.sh +34 -0
  19. package/apps/studio/bin/import-brand.sh +29 -0
  20. package/apps/studio/bin/import-tokens.sh +33 -0
  21. package/apps/studio/bin/photo-bg-remove.sh +119 -21
  22. package/apps/studio/bin/transcribe.sh +39 -0
  23. package/apps/studio/canvas-edit.ts +197 -2
  24. package/apps/studio/canvas-lib.tsx +115 -6
  25. package/apps/studio/client/app.jsx +1954 -685
  26. package/apps/studio/client/generate-dialog.jsx +352 -0
  27. package/apps/studio/client/github.js +14 -0
  28. package/apps/studio/client/inspector-controls.jsx +781 -0
  29. package/apps/studio/client/panels/BrandUploadPanel.jsx +233 -0
  30. package/apps/studio/client/panels/ChatPanel.jsx +15 -33
  31. package/apps/studio/client/panels/IntroVideoDialog.jsx +44 -0
  32. package/apps/studio/client/panels/OnboardingWizard.jsx +12 -0
  33. package/apps/studio/client/panels/ReadinessList.jsx +227 -7
  34. package/apps/studio/client/panels/SettingsPanel.jsx +828 -0
  35. package/apps/studio/client/panels/SetupChecklist.jsx +223 -0
  36. package/apps/studio/client/photo-knobs.jsx +167 -172
  37. package/apps/studio/client/styles/3-shell-maude.css +334 -28
  38. package/apps/studio/client/styles/4-components.css +223 -0
  39. package/apps/studio/client/styles/6-acp-chat.css +86 -0
  40. package/apps/studio/client/tour/quick-setup-tour.js +39 -0
  41. package/apps/studio/commands/annotation-strokes-command.ts +13 -3
  42. package/apps/studio/config.schema.json +70 -0
  43. package/apps/studio/context-menu.tsx +11 -2
  44. package/apps/studio/context.ts +16 -0
  45. package/apps/studio/design-setup-readiness.ts +115 -0
  46. package/apps/studio/dist/client.bundle.js +2097 -2088
  47. package/apps/studio/dist/styles.css +1 -1
  48. package/apps/studio/draw/palette.ts +34 -0
  49. package/apps/studio/footage/schema.test.ts +92 -0
  50. package/apps/studio/footage/schema.ts +179 -1
  51. package/apps/studio/generation/adapters/elevenlabs.ts +370 -0
  52. package/apps/studio/generation/adapters/gemini.ts +542 -0
  53. package/apps/studio/generation/adapters/groq.test.ts +80 -0
  54. package/apps/studio/generation/adapters/groq.ts +247 -0
  55. package/apps/studio/generation/audio-library.test.ts +95 -0
  56. package/apps/studio/generation/audio-library.ts +156 -0
  57. package/apps/studio/generation/captions.test.ts +78 -0
  58. package/apps/studio/generation/captions.ts +132 -0
  59. package/apps/studio/generation/download.ts +76 -0
  60. package/apps/studio/generation/elevenlabs.test.ts +223 -0
  61. package/apps/studio/generation/gemini.test.ts +350 -0
  62. package/apps/studio/generation/jobs.test.ts +128 -0
  63. package/apps/studio/generation/jobs.ts +243 -0
  64. package/apps/studio/generation/keys.test.ts +75 -0
  65. package/apps/studio/generation/keys.ts +162 -0
  66. package/apps/studio/generation/prefs.test.ts +95 -0
  67. package/apps/studio/generation/prefs.ts +82 -0
  68. package/apps/studio/generation/registry.ts +88 -0
  69. package/apps/studio/generation/types.test.ts +123 -0
  70. package/apps/studio/generation/types.ts +266 -0
  71. package/apps/studio/generation/whisper-models.test.ts +152 -0
  72. package/apps/studio/generation/whisper-models.ts +257 -0
  73. package/apps/studio/generation/whisper-spike-results.md +61 -0
  74. package/apps/studio/git/service.ts +3 -1
  75. package/apps/studio/http.ts +867 -9
  76. package/apps/studio/media/intro.mp4 +0 -0
  77. package/apps/studio/media-commit-chain.ts +119 -0
  78. package/apps/studio/paths.ts +10 -0
  79. package/apps/studio/readiness.ts +108 -20
  80. package/apps/studio/scaffold-design.ts +1145 -0
  81. package/apps/studio/server.ts +13 -1
  82. package/apps/studio/test/_helpers.ts +15 -2
  83. package/apps/studio/test/acp-bridge.test.ts +34 -1
  84. package/apps/studio/test/acp-env.test.ts +30 -0
  85. package/apps/studio/test/acp-plugin-bootstrap.test.ts +23 -57
  86. package/apps/studio/test/acp-session-plugins.test.ts +72 -1
  87. package/apps/studio/test/annotation-strokes-command.test.ts +17 -0
  88. package/apps/studio/test/annotations-layer.test.ts +174 -0
  89. package/apps/studio/test/canvas-origin-gate.test.ts +37 -0
  90. package/apps/studio/test/csp-canvas-shell.test.ts +11 -0
  91. package/apps/studio/test/csrf-write-guard.test.ts +23 -1
  92. package/apps/studio/test/design-setup-readiness.test.ts +155 -0
  93. package/apps/studio/test/element-structural-edit.test.ts +108 -0
  94. package/apps/studio/test/fixtures/fake-claude-auth.mjs +13 -0
  95. package/apps/studio/test/generate-route.test.ts +106 -0
  96. package/apps/studio/test/import-asset-browser.test.ts +64 -0
  97. package/apps/studio/test/import-asset-route.test.ts +71 -0
  98. package/apps/studio/test/import-asset.test.ts +399 -0
  99. package/apps/studio/test/import-brand-browser.test.ts +88 -0
  100. package/apps/studio/test/import-brand-route.test.ts +120 -0
  101. package/apps/studio/test/import-brand.test.ts +206 -0
  102. package/apps/studio/test/import-tokens.test.ts +722 -0
  103. package/apps/studio/test/media-commit-chain.test.ts +210 -0
  104. package/apps/studio/test/photo-bg-remove-validation.test.ts +71 -0
  105. package/apps/studio/test/readiness.test.ts +13 -11
  106. package/apps/studio/test/scaffold-design.test.ts +122 -0
  107. package/apps/studio/test/tour-overlay.test.tsx +16 -0
  108. package/apps/studio/tool-palette.tsx +11 -6
  109. package/apps/studio/ui-prefs.ts +130 -0
  110. package/apps/studio/use-canvas-media-drop.tsx +4 -2
  111. package/apps/studio/use-chrome-visibility.tsx +8 -2
  112. package/apps/studio/whats-new.json +96 -0
  113. package/apps/studio/ws.ts +5 -0
  114. package/cli/bin/maude.mjs +6 -6
  115. package/cli/commands/design.mjs +33 -0
  116. package/cli/lib/gitignore-block.mjs +1 -0
  117. package/cli/lib/pkg-root.mjs +107 -0
  118. package/cli/lib/pkg-root.test.mjs +79 -0
  119. package/cli/lib/reconstruct-toolset.test.mjs +194 -0
  120. package/cli/lib/update-check.mjs +8 -0
  121. package/package.json +12 -10
  122. package/plugins/design/dependencies.json +18 -0
@@ -859,6 +859,12 @@
859
859
  }
860
860
  .help-modal-body::-webkit-scrollbar { width: 6px; }
861
861
  .help-modal-body::-webkit-scrollbar-thumb { background: var(--u-border); }
862
+
863
+ /* Watch-the-intro player (DDR-166 Phase 1 / T2) — wider modal, no padding
864
+ around the video so it fills edge-to-edge. */
865
+ .help-modal--wide { width: min(960px, 100%); }
866
+ .intro-video-body { padding: 0; overflow: hidden; }
867
+ .intro-video-el { display: block; width: 100%; aspect-ratio: 16 / 9; background: #000; }
862
868
  .help-modal-body details {
863
869
  margin: 0 0 var(--u-s-2);
864
870
  border-bottom: 1px solid var(--u-border-subtle);
@@ -1652,3 +1658,220 @@
1652
1658
  background: var(--u-accent);
1653
1659
  color: var(--u-accent-fg);
1654
1660
  }
1661
+
1662
+ /* ── Settings — AI generation (feature-ai-media-generation, DDR-16x) ─────── */
1663
+ .st-settings-intro {
1664
+ font-size: var(--type-xs, 12px);
1665
+ color: var(--fg-3);
1666
+ line-height: 1.5;
1667
+ margin: 6px 0 12px;
1668
+ }
1669
+ .st-settings-intro code {
1670
+ font-family: var(--font-mono);
1671
+ font-size: 0.92em;
1672
+ color: var(--fg-2);
1673
+ }
1674
+ .st-provider-card {
1675
+ border: 1px solid var(--border-default);
1676
+ border-radius: var(--radius-sm);
1677
+ background: var(--bg-2);
1678
+ padding: 12px;
1679
+ margin-bottom: 10px;
1680
+ }
1681
+ .st-provider-hd {
1682
+ display: flex;
1683
+ align-items: center;
1684
+ gap: 8px;
1685
+ }
1686
+ .st-provider-name {
1687
+ font-family: var(--font-mono);
1688
+ font-size: var(--type-sm, 13px);
1689
+ color: var(--fg-1);
1690
+ font-weight: 600;
1691
+ }
1692
+ .st-provider-card .st-pill {
1693
+ display: inline-flex;
1694
+ align-items: center;
1695
+ gap: 3px;
1696
+ font-size: 10px;
1697
+ text-transform: uppercase;
1698
+ letter-spacing: 0.03em;
1699
+ color: var(--fg-3);
1700
+ border: 1px solid var(--border-default);
1701
+ border-radius: var(--radius-pill, 999px);
1702
+ padding: 1px 7px;
1703
+ }
1704
+ .st-provider-card .st-pill.is-local {
1705
+ color: var(--accent);
1706
+ border-color: var(--accent-muted, var(--accent));
1707
+ }
1708
+ .st-provider-configured {
1709
+ display: inline-flex;
1710
+ align-items: center;
1711
+ gap: 3px;
1712
+ margin-left: auto;
1713
+ font-size: 11px;
1714
+ color: var(--accent);
1715
+ }
1716
+ .st-provider-modalities {
1717
+ font-family: var(--font-mono);
1718
+ font-size: 11px;
1719
+ color: var(--fg-3);
1720
+ margin-top: 4px;
1721
+ }
1722
+ .st-provider-notes {
1723
+ font-size: 11px;
1724
+ color: var(--fg-3);
1725
+ line-height: 1.5;
1726
+ margin-top: 6px;
1727
+ }
1728
+ .st-provider-keylink {
1729
+ display: inline-flex;
1730
+ align-items: center;
1731
+ gap: 4px;
1732
+ font-size: 11px;
1733
+ color: var(--accent);
1734
+ text-decoration: none;
1735
+ margin-top: 6px;
1736
+ }
1737
+ .st-provider-keylink:hover {
1738
+ text-decoration: underline;
1739
+ }
1740
+ .st-provider-keyrow {
1741
+ display: flex;
1742
+ align-items: center;
1743
+ gap: 6px;
1744
+ margin-top: 10px;
1745
+ }
1746
+ .st-provider-keyinput {
1747
+ flex: 1;
1748
+ min-width: 0;
1749
+ }
1750
+ .st-provider-status {
1751
+ font-size: 11px;
1752
+ margin-top: 6px;
1753
+ }
1754
+
1755
+ /* ── Transcription-engine selector (Task 2.6) ───────────────────────────── */
1756
+ .st-engine-radios {
1757
+ display: flex;
1758
+ flex-direction: column;
1759
+ gap: 6px;
1760
+ margin-top: 10px;
1761
+ }
1762
+ .st-engine-radio {
1763
+ display: flex;
1764
+ align-items: flex-start;
1765
+ gap: 9px;
1766
+ padding: 9px 11px;
1767
+ border: 1px solid var(--border-default);
1768
+ border-radius: var(--radius-sm);
1769
+ background: var(--bg-1);
1770
+ cursor: pointer;
1771
+ }
1772
+ .st-engine-radio.is-selected {
1773
+ border-color: var(--accent);
1774
+ background: var(--bg-2);
1775
+ }
1776
+ .st-engine-radio input {
1777
+ margin-top: 2px;
1778
+ accent-color: var(--accent);
1779
+ }
1780
+ .st-engine-radio-body {
1781
+ display: flex;
1782
+ flex-direction: column;
1783
+ gap: 2px;
1784
+ }
1785
+ .st-engine-radio-label {
1786
+ font-size: var(--type-sm, 13px);
1787
+ color: var(--fg-1);
1788
+ font-weight: 600;
1789
+ }
1790
+ .st-engine-radio-note {
1791
+ font-size: 11px;
1792
+ color: var(--fg-3);
1793
+ }
1794
+
1795
+ /* ── Local whisper model download (Task 2.7) ────────────────────────────── */
1796
+ .st-model-list {
1797
+ display: flex;
1798
+ flex-direction: column;
1799
+ gap: 8px;
1800
+ margin-top: 10px;
1801
+ }
1802
+ .st-model-row {
1803
+ display: flex;
1804
+ align-items: flex-start;
1805
+ justify-content: space-between;
1806
+ gap: 10px;
1807
+ padding: 9px 11px;
1808
+ border: 1px solid var(--border-default);
1809
+ border-radius: var(--radius-sm);
1810
+ background: var(--bg-1);
1811
+ }
1812
+ .st-model-info {
1813
+ display: flex;
1814
+ flex-direction: column;
1815
+ gap: 3px;
1816
+ min-width: 0;
1817
+ }
1818
+ .st-model-label {
1819
+ display: flex;
1820
+ align-items: center;
1821
+ gap: 7px;
1822
+ flex-wrap: wrap;
1823
+ font-size: var(--type-sm, 13px);
1824
+ color: var(--fg-1);
1825
+ font-weight: 600;
1826
+ }
1827
+ .st-model-size {
1828
+ font-size: 11px;
1829
+ color: var(--fg-3);
1830
+ font-weight: 400;
1831
+ }
1832
+ .st-model-progress {
1833
+ font-size: 11px;
1834
+ color: var(--accent);
1835
+ font-variant-numeric: tabular-nums;
1836
+ }
1837
+ .st-model-actions {
1838
+ display: flex;
1839
+ align-items: center;
1840
+ gap: 8px;
1841
+ flex-shrink: 0;
1842
+ }
1843
+
1844
+ /* ── Generate dialog (feature-ai-media-generation, DDR-16x) ─────────────── */
1845
+ .st-gen-prompt {
1846
+ width: 100%;
1847
+ resize: vertical;
1848
+ min-height: 60px;
1849
+ font-family: var(--font-sans, system-ui, sans-serif);
1850
+ margin-bottom: 10px;
1851
+ }
1852
+ .st-gen-go {
1853
+ color: var(--accent);
1854
+ border-color: var(--accent-muted, var(--accent));
1855
+ }
1856
+ .st-gen-result {
1857
+ display: flex;
1858
+ gap: 10px;
1859
+ align-items: flex-start;
1860
+ margin-top: 12px;
1861
+ padding: 10px;
1862
+ border: 1px solid var(--border-default);
1863
+ border-radius: var(--radius-sm);
1864
+ background: var(--bg-2);
1865
+ }
1866
+ .st-gen-thumb {
1867
+ width: 96px;
1868
+ height: 96px;
1869
+ object-fit: cover;
1870
+ border-radius: var(--radius-sm);
1871
+ background: var(--bg-3);
1872
+ }
1873
+ .st-gen-result-actions {
1874
+ display: flex;
1875
+ flex-direction: column;
1876
+ gap: 6px;
1877
+ }
@@ -1181,7 +1181,93 @@
1181
1181
  }
1182
1182
  .rdy-copy:hover { color: var(--fg-0); background: var(--bg-3); }
1183
1183
  .rdy-foot { display: flex; justify-content: flex-end; }
1184
+ /* DDR-166 T0c/T0d — verbatim install command + Sign-in action rows. */
1185
+ .rdy-cmd {
1186
+ margin-top: var(--space-2); display: flex; gap: var(--space-2);
1187
+ align-items: center; flex-wrap: wrap;
1188
+ }
1189
+ .rdy-cmd-tx {
1190
+ font-family: var(--font-mono); font-size: var(--type-xs); color: var(--fg-0);
1191
+ background: var(--bg-3); padding: var(--space-1) var(--space-2);
1192
+ border-radius: var(--radius-xs); overflow-wrap: anywhere; flex: 1 1 auto;
1193
+ }
1194
+ .rdy-fix-tx--err { color: var(--status-error); }
1195
+ .rdy-fallback-label { display: block; margin-top: var(--space-2); color: var(--fg-3); }
1196
+ /* DDR-166 Decision 3 — the resolved-binary disclosure shown before Sign-in. */
1197
+ .rdy-resolved-path { display: block; margin-top: var(--space-2); color: var(--fg-3); word-break: break-all; }
1198
+ .rdy-resolved-path code { font-family: var(--font-mono); color: var(--fg-1); }
1199
+ .rdy-signin {
1200
+ flex-shrink: 0; cursor: pointer;
1201
+ font-size: var(--type-xs); font-weight: 600; color: var(--bg-0);
1202
+ background: var(--accent); border: 1px solid var(--accent);
1203
+ border-radius: var(--radius-xs); padding: 3px 10px;
1204
+ }
1205
+ .rdy-signin:hover { filter: brightness(1.08); }
1184
1206
  /* Help ▸ Check AI editing readiness… modal (reuses .help-modal chrome). */
1185
1207
  .rdy-modal { max-width: 520px; }
1186
1208
  .rdy-modal-note { margin: 0 0 var(--space-3); color: var(--fg-2); font-size: var(--type-sm); line-height: var(--lh-md); }
1209
+ /* DDR-166 Decision 5 — auto-setup opt-out, mirrors .ob-crash-optin. */
1210
+ .rdy-autosetup-toggle { display: flex; align-items: flex-start; gap: var(--space-2); margin-top: var(--space-3); color: var(--fg-2); font-size: var(--type-xs); line-height: var(--lh-md); cursor: pointer; text-align: left; }
1211
+ .rdy-autosetup-toggle input { margin-top: 2px; flex: 0 0 auto; accent-color: var(--accent); cursor: pointer; }
1212
+ }
1213
+
1214
+ /* DDR-166 plan, Phase 2 (T7/T8) — design-setup checklist (SetupChecklist.jsx).
1215
+ Mirrors .rdy-* above (same present/missing two-state shape, no remediation
1216
+ copy — this probe never offers an automated fix, just progress). */
1217
+ @layer components {
1218
+ .setup-cl { display: flex; flex-direction: column; gap: var(--space-3); }
1219
+ .setup-cl-list {
1220
+ list-style: none; margin: 0; padding: 0;
1221
+ display: flex; flex-direction: column; gap: var(--space-2);
1222
+ }
1223
+ .setup-cl-row {
1224
+ display: flex; gap: var(--space-2); align-items: flex-start;
1225
+ padding: var(--space-2) var(--space-3);
1226
+ border: 1px solid var(--border-subtle); border-radius: var(--radius-sm);
1227
+ background: var(--bg-2); text-align: left;
1228
+ }
1229
+ .setup-cl-ic { flex-shrink: 0; margin-top: 1px; }
1230
+ .setup-cl-row--present .setup-cl-ic { color: var(--status-success); }
1231
+ .setup-cl-row--missing .setup-cl-ic { color: var(--fg-3); }
1232
+ .setup-cl-tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1233
+ .setup-cl-label { font-size: var(--type-sm); font-weight: 600; color: var(--fg-0); }
1234
+ .setup-cl-detail { font-size: var(--type-xs); color: var(--fg-2); line-height: var(--lh-xs); }
1235
+ .setup-cl-foot { display: flex; justify-content: flex-end; gap: var(--space-2); flex-wrap: wrap; }
1236
+ .setup-cl-brand { text-decoration: none; }
1237
+ .setup-cl-modal { max-width: 520px; }
1238
+ .setup-cl-note { margin: 0 0 var(--space-3); color: var(--fg-2); font-size: var(--type-sm); line-height: var(--lh-md); }
1239
+ }
1240
+
1241
+ /* T12 (DDR-173) — the in-app Brand-upload panel (BrandUploadPanel.jsx).
1242
+ Reuses .help-modal-* chrome; new rules cover the pick/result content only. */
1243
+ @layer components {
1244
+ .brand-up-modal { max-width: 480px; }
1245
+ .brand-up-note { margin: 0 0 var(--space-4); color: var(--fg-2); font-size: var(--type-sm); line-height: var(--lh-md); }
1246
+ .brand-up-pick { display: flex; justify-content: center; padding: var(--space-5) 0; }
1247
+ .brand-up-error { margin: var(--space-2) 0 0; color: var(--status-error); font-size: var(--type-sm); }
1248
+ .brand-up-empty { margin: 0; color: var(--fg-3); font-size: var(--type-xs); line-height: var(--lh-sm); }
1249
+ .brand-up-preview {
1250
+ display: block; max-width: 100%; max-height: 120px; margin: 0 auto var(--space-4);
1251
+ border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); background: var(--bg-3);
1252
+ padding: var(--space-3);
1253
+ }
1254
+ .brand-up-sechd {
1255
+ margin: var(--space-4) 0 var(--space-2); font-size: var(--type-xs); font-weight: 600;
1256
+ text-transform: uppercase; letter-spacing: var(--tracking-wide); color: var(--fg-2);
1257
+ }
1258
+ .brand-up-swatches { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-2); }
1259
+ .brand-up-swatch { display: flex; align-items: center; gap: var(--space-2); }
1260
+ .brand-up-swatch-chip {
1261
+ display: inline-block; width: 20px; height: 20px; border-radius: var(--radius-xs);
1262
+ border: 1px solid var(--border-default);
1263
+ }
1264
+ .brand-up-swatch-value { font-family: var(--font-mono); font-size: var(--type-xs); color: var(--fg-2); }
1265
+ .brand-up-fonts { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-2); }
1266
+ .brand-up-font-chip {
1267
+ padding: 2px var(--space-2); border-radius: var(--radius-pill); border: 1px solid var(--border-default);
1268
+ background: var(--bg-2); font-size: var(--type-xs); color: var(--fg-1);
1269
+ }
1270
+ .brand-up-nextstep { margin: var(--space-4) 0 0; font-size: var(--type-sm); color: var(--fg-1); line-height: var(--lh-md); }
1271
+ .brand-up-nextstep code { font-family: var(--font-mono); background: var(--bg-3); padding: 1px 4px; border-radius: var(--radius-xs); }
1272
+ .brand-up-actions { display: flex; gap: var(--space-2); margin-top: var(--space-3); }
1187
1273
  }
@@ -0,0 +1,39 @@
1
+ // quick-setup-tour.js — DDR-166 plan, Phase 2 (T7). The "empty → design system
2
+ // → first canvas → first AI edit" journey, on the existing tour engine
3
+ // (overlay.jsx), never a fork — same convention as usage-tour.js/collab-tour.js.
4
+ //
5
+ // Native, no-terminal posture (DDR-126/128): this deck can't execute
6
+ // `/design:setup-ds` for the user (the canvas iframe is cross-origin — DDR-054 —
7
+ // and the shell has no terminal). It coach-marks the Assistant panel instead:
8
+ // point at it, tell the user what to type, let THEM send it. The terminal
9
+ // `/design:*` path stays the power-user route.
10
+
11
+ export const QUICK_SETUP_TOUR = [
12
+ {
13
+ // Centered, no target — the overview step.
14
+ title: "Let's get you set up",
15
+ body: 'Three short steps: a design system for your brand, a first canvas, then your first AI edit. Everything after this happens right here — no terminal.',
16
+ },
17
+ {
18
+ target: "[data-tour='sidebar']",
19
+ title: 'Your canvases will live here',
20
+ body: "Empty for now. Once you have a design system, this fills up with real screens — click any one to open it.",
21
+ placement: 'right',
22
+ },
23
+ {
24
+ target: "[data-testid='assistant-toggle']",
25
+ title: 'Ask the Assistant to build your design system',
26
+ body: 'Open the Assistant and describe your brand — colors, type, the vibe you want. It sets up a real design system for you. Already have one? Say so and it can bring it in.',
27
+ placement: 'left',
28
+ },
29
+ {
30
+ target: "[data-tour='viewport']",
31
+ title: 'Your first canvas appears here',
32
+ body: "Once the design system is ready, ask for a first screen and watch it land right in this space, ready to edit.",
33
+ },
34
+ {
35
+ // Centered — the close-out step.
36
+ title: "You're ready",
37
+ body: 'Hold ⌘ and click anything on a canvas to point at it, describe the change in the Assistant, and watch it update live. Restart this tour any time from Help ▸ Quick setup.',
38
+ },
39
+ ];
@@ -27,8 +27,18 @@ import { registerCommand } from '../undo-stack.ts';
27
27
  * Push-once callable that submits a full `Stroke[]` to the server (or its
28
28
  * test stub) AND updates the iframe's local strokes state. The
29
29
  * annotations-layer `putStrokes` is the production binding.
30
+ *
31
+ * `before` is the baseline THIS command's `next` was computed from — passed
32
+ * through so the consumer can distinguish "an id `prev` still has that
33
+ * `before` already lacked too" (a genuinely concurrent addition, fold it in)
34
+ * from "an id `before` had that `next` deliberately dropped" (this command's
35
+ * own delete — must stay dropped even if React's rendered state hasn't
36
+ * caught up yet). See `reconcileCommit` in annotations-layer.tsx.
30
37
  */
31
- export type StrokesPutFn = (next: readonly Stroke[]) => void | Promise<void>;
38
+ export type StrokesPutFn = (
39
+ next: readonly Stroke[],
40
+ before: readonly Stroke[]
41
+ ) => void | Promise<void>;
32
42
 
33
43
  export interface AnnotationStrokesPayload {
34
44
  before: readonly Stroke[];
@@ -58,10 +68,10 @@ export function createAnnotationStrokesCommand(init: AnnotationStrokesCommandIni
58
68
  kind,
59
69
  label,
60
70
  async do() {
61
- await putFn(after.map(cloneStroke));
71
+ await putFn(after.map(cloneStroke), before.map(cloneStroke));
62
72
  },
63
73
  async undo() {
64
- await putFn(before.map(cloneStroke));
74
+ await putFn(before.map(cloneStroke), after.map(cloneStroke));
65
75
  },
66
76
  };
67
77
  }
@@ -231,6 +231,76 @@
231
231
  }
232
232
  },
233
233
  "additionalProperties": false
234
+ },
235
+ "generation": {
236
+ "type": "object",
237
+ "description": "BYOK AI-media generation preferences (feature-ai-media-generation, DDR-16x). NON-SECRET ONLY — provider API keys are NEVER in this committed config; they live in the OS keychain (native) or ~/.config/maude/keys.json mode 0600 (browser). This block only carries routing/UI preferences. Hot-reloadable — Settings saves take effect without a server restart.",
238
+ "properties": {
239
+ "defaultImageProvider": {
240
+ "type": "string",
241
+ "description": "Provider id used when an image-generation request leaves the provider unspecified (e.g. 'gemini'). Must be a registered provider that supports the image modality.",
242
+ "default": "gemini"
243
+ },
244
+ "defaultAudioProvider": {
245
+ "type": "string",
246
+ "description": "Provider id used when an audio-generation request (music / sfx / voiceover) leaves the provider unspecified. Must be a registered provider that supports the audio modality (v1: 'elevenlabs').",
247
+ "default": "elevenlabs"
248
+ },
249
+ "defaultModels": {
250
+ "type": "object",
251
+ "description": "Per-provider default model id (e.g. { \"gemini\": \"gemini-2.5-flash-image\" }). Used when a request leaves the model unspecified.",
252
+ "additionalProperties": { "type": "string" },
253
+ "default": {}
254
+ },
255
+ "preferLocalWhenAvailable": {
256
+ "type": "boolean",
257
+ "description": "When a local runtime (Ollama / ComfyUI / Draw Things, post-v1) is detected on loopback, prefer it over a cloud provider for the same modality (free, offline, private). Inert until local engines ship (Phase 5).",
258
+ "default": false
259
+ },
260
+ "transcription": {
261
+ "type": "object",
262
+ "description": "Speech-to-text / subtitle engine preference (Task 2.6, DDR-164). The engine is an EXPLICIT user choice, never an automatic fallback: a chosen-but-unavailable engine errors clearly and Maude never silently switches to another. `maude design transcribe --provider` overrides this per-call.",
263
+ "properties": {
264
+ "provider": {
265
+ "type": "string",
266
+ "enum": ["whisper", "elevenlabs", "groq"],
267
+ "description": "Which engine transcribes when no --provider flag is given. `whisper` = local whisper.cpp (free, offline, no key); `elevenlabs` = ElevenLabs Scribe (cloud, key); `groq` = Groq Whisper (cloud, key). Absent ⇒ whisper.",
268
+ "default": "whisper"
269
+ },
270
+ "model": {
271
+ "type": "string",
272
+ "description": "Optional engine-specific model id override (e.g. a Groq 'whisper-large-v3-turbo', an ElevenLabs 'scribe_v1', or a local ggml model name). Absent ⇒ the engine's own default."
273
+ },
274
+ "whisperModel": {
275
+ "type": "string",
276
+ "enum": ["tiny", "base", "base.en", "small", "large-v3-turbo"],
277
+ "description": "Preferred managed local whisper.cpp model id (Task 2.7). Downloaded via Settings → Subtitles → 'Download model' into a per-machine cache; `maude design transcribe --provider whisper` auto-resolves it (no --model). Absent ⇒ any downloaded multilingual model. 'base' is the recommended default; '.en' variants are English-only."
278
+ }
279
+ },
280
+ "additionalProperties": false
281
+ },
282
+ "providers": {
283
+ "type": "object",
284
+ "description": "Per-provider non-secret toggles keyed by provider id. `enabled: false` hides a provider from the generate UI even if a key is present. `localEndpoint` overrides the default host:port for a local runtime.",
285
+ "additionalProperties": {
286
+ "type": "object",
287
+ "properties": {
288
+ "enabled": {
289
+ "type": "boolean",
290
+ "description": "Whether this provider is offered in the generate UI. Default true.",
291
+ "default": true
292
+ },
293
+ "localEndpoint": {
294
+ "type": "string",
295
+ "description": "Override host:port for a local runtime provider (e.g. '127.0.0.1:11434' for Ollama). Ignored for cloud providers."
296
+ }
297
+ },
298
+ "additionalProperties": false
299
+ },
300
+ "default": {}
301
+ }
302
+ },
303
+ "additionalProperties": false
234
304
  }
235
305
  }
236
306
  }
@@ -216,10 +216,19 @@ const MENU_CSS = `
216
216
  font: inherit;
217
217
  color: inherit;
218
218
  }
219
- .dc-context-menu .dc-menu-item:hover,
219
+ .dc-context-menu .dc-menu-item:hover {
220
+ background: color-mix(in oklab, var(--maude-chrome-fg-0, #1c1917) 8%, transparent);
221
+ }
220
222
  .dc-context-menu .dc-menu-item:focus-visible {
223
+ /* fix-photo-editor-followup-debt Task 16 — was outline: none (an
224
+ ~1.05:1-contrast, effectively invisible focus ring). Reuses
225
+ --maude-hud-accent (canvas-lib.tsx's own established focus-visible
226
+ token for this chrome family — artboard labels, zoom toolbar) rather
227
+ than inventing a new one; -2px inset offset matches that same
228
+ convention for a control inside a bordered container. */
221
229
  background: color-mix(in oklab, var(--maude-chrome-fg-0, #1c1917) 8%, transparent);
222
- outline: none;
230
+ outline: 2px solid var(--maude-hud-accent, #d63b1f);
231
+ outline-offset: -2px;
223
232
  }
224
233
  .dc-context-menu .dc-menu-item[disabled] {
225
234
  opacity: 0.45;
@@ -55,9 +55,25 @@ export interface DevServerConfig {
55
55
  newCanvasDir: string;
56
56
  newComponentDir: string;
57
57
  linkedHub?: LinkedHub;
58
+ /**
59
+ * feature-ai-media-generation (DDR-16x) — BYOK generation preferences.
60
+ * NON-SECRET ONLY: provider keys live in the OS keychain / ~/.config/maude/
61
+ * keys.json (0600), NEVER here. Hot-reloadable via the full in-place cfg swap
62
+ * in reloadConfig (only designRoot + linkedHub are boot-pinned). See
63
+ * config.schema.json for the field contract.
64
+ */
65
+ generation?: GenerationConfig;
58
66
  _source: ConfigSource;
59
67
  }
60
68
 
69
+ /** Non-secret AI-media generation preferences (DDR-16x). Keys are NOT here. */
70
+ export interface GenerationConfig {
71
+ defaultImageProvider?: string;
72
+ defaultModels?: Record<string, string>;
73
+ preferLocalWhenAvailable?: boolean;
74
+ providers?: Record<string, { enabled?: boolean; localEndpoint?: string }>;
75
+ }
76
+
61
77
  const DEFAULT_CONFIG: Omit<DevServerConfig, '_source'> = {
62
78
  name: 'Design',
63
79
  projectLabel: null,
@@ -0,0 +1,115 @@
1
+ // Design-setup readiness probe (DDR-166 plan, Phase 2 / T6). Backs
2
+ // `GET /_api/setup-readiness`.
3
+ //
4
+ // Distinct from readiness.ts (which probes the AI-EDITING dependency chain —
5
+ // claude/maude/plugins on the user's machine). This probes the PROJECT's own
6
+ // setup progress: has it moved past the empty scaffold write_minimal_design()
7
+ // leaves behind, toward a project with a real design system, a first canvas,
8
+ // and brand assets. Read-only, mirrors probeReadiness()'s shape and posture —
9
+ // never installs or mutates anything.
10
+
11
+ import { existsSync, readdirSync } from 'node:fs';
12
+ import path from 'node:path';
13
+ import { findHtmlFiles } from './api.ts';
14
+ import type { Context } from './context.ts';
15
+
16
+ export type SetupReadinessStatus = 'present' | 'missing';
17
+
18
+ export interface SetupReadinessItem {
19
+ id: 'project' | 'design-system' | 'first-canvas' | 'brand-assets';
20
+ label: string;
21
+ status: SetupReadinessStatus;
22
+ detail: string;
23
+ }
24
+
25
+ export interface SetupReadinessReport {
26
+ /** True once every item is `present`. */
27
+ ready: boolean;
28
+ items: SetupReadinessItem[];
29
+ }
30
+
31
+ /** True when `dsDirAbs/preview/` has a `logo.*` specimen (DDR-141 Tier-0). */
32
+ function hasLogoSpecimen(dsDirAbs: string): boolean {
33
+ const previewDir = path.join(dsDirAbs, 'preview');
34
+ let entries: string[];
35
+ try {
36
+ entries = readdirSync(previewDir);
37
+ } catch {
38
+ return false;
39
+ }
40
+ return entries.some((f) => /^logo\./i.test(f));
41
+ }
42
+
43
+ /**
44
+ * Side-effect-free. `ctx.cfg` is already schema-clamped (context.ts
45
+ * `clampToDesignRoot`), so every `designSystems[].path`/`tokensCssRel` and
46
+ * `canvasGroups[].path` used here is guaranteed to resolve inside `designRoot`.
47
+ */
48
+ export async function probeSetupReadiness(ctx: Context): Promise<SetupReadinessReport> {
49
+ const { cfg, paths } = ctx;
50
+ const items: SetupReadinessItem[] = [];
51
+
52
+ // A real, on-disk `.design/config.json` (vs. the in-memory defaults fallback
53
+ // `createContext()` uses when the file is missing/invalid) — the signal that
54
+ // this is a genuinely-initialized project, not just the Rust
55
+ // write_minimal_design() scaffold rendering an empty shell.
56
+ const hasRealConfig = cfg._source === '.design/config.json';
57
+ items.push({
58
+ id: 'project',
59
+ label: 'Project set up',
60
+ status: hasRealConfig ? 'present' : 'missing',
61
+ detail: hasRealConfig
62
+ ? `“${cfg.name}” — a real .design/config.json is on disk.`
63
+ : 'No .design/config.json yet — this is the empty starter scaffold.',
64
+ });
65
+
66
+ const designSystems = cfg.designSystems ?? [];
67
+ const dsWithTokens = designSystems.find((ds) => {
68
+ const rel = ds.tokensCssRel ?? path.posix.join(ds.path, 'colors_and_type.css');
69
+ return existsSync(path.join(paths.designRoot, rel));
70
+ });
71
+ items.push({
72
+ id: 'design-system',
73
+ label: 'Design system',
74
+ status: dsWithTokens ? 'present' : 'missing',
75
+ detail: dsWithTokens
76
+ ? `“${dsWithTokens.name}” — tokens are on disk.`
77
+ : designSystems.length
78
+ ? 'Declared in config, but the tokens file is missing.'
79
+ : 'None yet — ask the Assistant to set one up, or bring your own brand.',
80
+ });
81
+
82
+ // Any real canvas outside the DS specimen groups (canvasGroups whose path
83
+ // matches /system/i are DS preview specimens, not user canvases).
84
+ const canvasGroups = (cfg.canvasGroups ?? []).filter((g) => !/system/i.test(g.path));
85
+ let firstCanvas: string | null = null;
86
+ for (const g of canvasGroups) {
87
+ const groupAbs = path.join(paths.designRoot, g.path);
88
+ const groupRel = path.posix.join(paths.designRel, g.path);
89
+ const files = await findHtmlFiles(groupAbs, groupRel);
90
+ if (files.length) {
91
+ firstCanvas = files[0];
92
+ break;
93
+ }
94
+ }
95
+ items.push({
96
+ id: 'first-canvas',
97
+ label: 'First canvas',
98
+ status: firstCanvas ? 'present' : 'missing',
99
+ detail: firstCanvas ? `${path.basename(firstCanvas)} is open for editing.` : 'No canvas yet.',
100
+ });
101
+
102
+ const dsWithLogo = designSystems.find((ds) =>
103
+ hasLogoSpecimen(path.join(paths.designRoot, ds.path))
104
+ );
105
+ items.push({
106
+ id: 'brand-assets',
107
+ label: 'Brand assets',
108
+ status: dsWithLogo ? 'present' : 'missing',
109
+ detail: dsWithLogo
110
+ ? `“${dsWithLogo.name}” has a stored logo specimen.`
111
+ : 'No logo/brand specimen on file yet.',
112
+ });
113
+
114
+ return { ready: items.every((i) => i.status === 'present'), items };
115
+ }