@syncedco/flow 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +2 -1
- package/bin/synced-flow.mjs +362 -3
- package/components.css +190 -2
- package/docs/api-contract.md +3 -3
- package/docs/audit-2026-06.md +113 -0
- package/docs/config-reference.md +27 -0
- package/docs/patterns.md +7 -0
- package/docs/system-primitives.md +8 -5
- package/docs/tokens.md +7 -1
- package/docs/why-synced-flow.md +1 -1
- package/layout.css +91 -1
- package/package.json +3 -2
- package/skills/synced-flow/SKILL.md +23 -6
- package/src/tokens.mjs +10 -0
- package/styles.css +328 -4
- package/tokens.css +45 -0
- package/utilities.css +2 -1
package/components.css
CHANGED
|
@@ -432,6 +432,16 @@
|
|
|
432
432
|
to { transform: translateX(-50%); }
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
+
@media (prefers-reduced-motion: reduce) {
|
|
436
|
+
:where(.sf-button:hover, .sf-card--interactive:hover) {
|
|
437
|
+
transform: none;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.sf-marquee__track {
|
|
441
|
+
animation: none;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
435
445
|
.sf-command-list {
|
|
436
446
|
display: grid;
|
|
437
447
|
font-family: var(--sf-font-mono);
|
|
@@ -837,7 +847,7 @@
|
|
|
837
847
|
}
|
|
838
848
|
|
|
839
849
|
.sf-filter-bar {
|
|
840
|
-
align-items:
|
|
850
|
+
align-items: var(--sf-filter-bar-align, center);
|
|
841
851
|
background: var(--sf-colour-surface);
|
|
842
852
|
border: 1px solid var(--sf-colour-border);
|
|
843
853
|
border-radius: var(--sf-radius-panel);
|
|
@@ -885,6 +895,76 @@
|
|
|
885
895
|
--sf-data-table-padding-inline: var(--sf-space-xs);
|
|
886
896
|
}
|
|
887
897
|
|
|
898
|
+
.sf-data-table :where(th button, .sf-table-sort) {
|
|
899
|
+
align-items: center;
|
|
900
|
+
color: inherit;
|
|
901
|
+
display: inline-flex;
|
|
902
|
+
font: inherit;
|
|
903
|
+
font-weight: inherit;
|
|
904
|
+
gap: var(--sf-space-3xs);
|
|
905
|
+
padding: 0;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.sf-data-table :where(th button, .sf-table-sort)[aria-sort]::after {
|
|
909
|
+
color: var(--sf-colour-subtle);
|
|
910
|
+
content: "\2195";
|
|
911
|
+
font-size: var(--sf-step--2);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
.sf-data-table :where(th button, .sf-table-sort)[aria-sort="ascending"]::after { content: "\2191"; }
|
|
915
|
+
.sf-data-table :where(th button, .sf-table-sort)[aria-sort="descending"]::after { content: "\2193"; }
|
|
916
|
+
|
|
917
|
+
.sf-data-table tr[data-clickable="true"] {
|
|
918
|
+
cursor: pointer;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
.sf-data-table tr[data-clickable="true"]:focus-within {
|
|
922
|
+
outline: 0.125rem solid var(--sf-colour-ring);
|
|
923
|
+
outline-offset: -0.125rem;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
@container (max-width: 38rem) {
|
|
927
|
+
.sf-table-wrap[data-view="cards"] {
|
|
928
|
+
border: 0;
|
|
929
|
+
overflow: visible;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
.sf-table-wrap[data-view="cards"] .sf-data-table,
|
|
933
|
+
.sf-table-wrap[data-view="cards"] .sf-data-table :where(thead, tbody, tr, th, td) {
|
|
934
|
+
display: block;
|
|
935
|
+
min-inline-size: 0;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
.sf-table-wrap[data-view="cards"] .sf-data-table thead {
|
|
939
|
+
display: none;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
.sf-table-wrap[data-view="cards"] .sf-data-table tr {
|
|
943
|
+
background: var(--sf-colour-surface);
|
|
944
|
+
border: 1px solid var(--sf-colour-border);
|
|
945
|
+
border-radius: var(--sf-radius-panel);
|
|
946
|
+
margin-block-end: var(--sf-space-xs);
|
|
947
|
+
padding: var(--sf-space-xs);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
.sf-table-wrap[data-view="cards"] .sf-data-table td {
|
|
951
|
+
align-items: center;
|
|
952
|
+
border: 0;
|
|
953
|
+
display: flex;
|
|
954
|
+
gap: var(--sf-space-s);
|
|
955
|
+
justify-content: space-between;
|
|
956
|
+
padding-block: var(--sf-space-2xs);
|
|
957
|
+
padding-inline: 0;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
.sf-table-wrap[data-view="cards"] .sf-data-table td::before {
|
|
961
|
+
color: var(--sf-colour-muted);
|
|
962
|
+
content: attr(data-label);
|
|
963
|
+
font-size: var(--sf-step--1);
|
|
964
|
+
font-weight: 800;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
888
968
|
.sf-status {
|
|
889
969
|
align-items: center;
|
|
890
970
|
background: var(--sf-status-bg, var(--sf-colour-surface-alt));
|
|
@@ -900,6 +980,12 @@
|
|
|
900
980
|
padding-inline: .7em;
|
|
901
981
|
}
|
|
902
982
|
|
|
983
|
+
.sf-status[data-size="sm"] {
|
|
984
|
+
font-size: var(--sf-step--2);
|
|
985
|
+
min-block-size: 1.35rem;
|
|
986
|
+
padding-inline: .55em;
|
|
987
|
+
}
|
|
988
|
+
|
|
903
989
|
.sf-status::before {
|
|
904
990
|
background: currentColor;
|
|
905
991
|
block-size: .42em;
|
|
@@ -908,6 +994,10 @@
|
|
|
908
994
|
inline-size: .42em;
|
|
909
995
|
}
|
|
910
996
|
|
|
997
|
+
:where(.sf-status[data-dot="false"], .sf-status--label)::before {
|
|
998
|
+
content: none;
|
|
999
|
+
}
|
|
1000
|
+
|
|
911
1001
|
.sf-status[data-tone="success"] {
|
|
912
1002
|
--sf-status-bg: var(--sf-colour-success-soft);
|
|
913
1003
|
--sf-status-border: color-mix(in oklch, var(--sf-colour-success) 34%, transparent);
|
|
@@ -1055,6 +1145,13 @@
|
|
|
1055
1145
|
gap: var(--sf-form-gap, var(--sf-space-s-m));
|
|
1056
1146
|
}
|
|
1057
1147
|
|
|
1148
|
+
.sf-form[data-density="compact"],
|
|
1149
|
+
.sf-field[data-density="compact"] {
|
|
1150
|
+
--sf-form-gap: var(--sf-space-xs);
|
|
1151
|
+
--sf-input-block-size: 2.25rem;
|
|
1152
|
+
--sf-input-padding-inline: var(--sf-space-xs);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1058
1155
|
.sf-fieldset {
|
|
1059
1156
|
border: 1px solid var(--sf-colour-border);
|
|
1060
1157
|
border-radius: var(--sf-radius-panel);
|
|
@@ -1120,6 +1217,45 @@
|
|
|
1120
1217
|
transition: background-color var(--sf-duration-fast) var(--sf-ease-standard), border-color var(--sf-duration-fast) var(--sf-ease-standard), box-shadow var(--sf-duration-fast) var(--sf-ease-standard);
|
|
1121
1218
|
}
|
|
1122
1219
|
|
|
1220
|
+
.sf-search {
|
|
1221
|
+
position: relative;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
.sf-search::before {
|
|
1225
|
+
block-size: .75em;
|
|
1226
|
+
border: 0.125em solid currentColor;
|
|
1227
|
+
border-radius: var(--sf-radius-full);
|
|
1228
|
+
color: var(--sf-colour-subtle);
|
|
1229
|
+
content: "";
|
|
1230
|
+
inline-size: .75em;
|
|
1231
|
+
inset-block-start: 50%;
|
|
1232
|
+
inset-inline-start: var(--sf-space-xs);
|
|
1233
|
+
line-height: 1;
|
|
1234
|
+
position: absolute;
|
|
1235
|
+
transform: translateY(-58%);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
.sf-search::after {
|
|
1239
|
+
background: var(--sf-colour-subtle);
|
|
1240
|
+
block-size: .125em;
|
|
1241
|
+
content: "";
|
|
1242
|
+
inline-size: .45em;
|
|
1243
|
+
inset-block-start: 50%;
|
|
1244
|
+
inset-inline-start: calc(var(--sf-space-xs) + .58em);
|
|
1245
|
+
position: absolute;
|
|
1246
|
+
transform: translateY(120%) rotate(45deg);
|
|
1247
|
+
transform-origin: inline-start center;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
.sf-search > .sf-input {
|
|
1251
|
+
padding-inline-start: var(--sf-space-l);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
.sf-help[data-density="compact"],
|
|
1255
|
+
.sf-error[data-density="compact"] {
|
|
1256
|
+
font-size: var(--sf-step--2);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1123
1259
|
.sf-textarea {
|
|
1124
1260
|
min-block-size: var(--sf-textarea-block-size, 8rem);
|
|
1125
1261
|
padding-block: var(--sf-space-xs);
|
|
@@ -1240,7 +1376,7 @@
|
|
|
1240
1376
|
.sf-dialog::backdrop,
|
|
1241
1377
|
.sf-popover::backdrop,
|
|
1242
1378
|
.sf-drawer::backdrop {
|
|
1243
|
-
background:
|
|
1379
|
+
background: var(--sf-colour-backdrop);
|
|
1244
1380
|
}
|
|
1245
1381
|
|
|
1246
1382
|
.sf-dialog__header,
|
|
@@ -1266,6 +1402,11 @@
|
|
|
1266
1402
|
justify-content: flex-end;
|
|
1267
1403
|
}
|
|
1268
1404
|
|
|
1405
|
+
.sf-dialog[data-layout="form"] .sf-dialog__body {
|
|
1406
|
+
display: grid;
|
|
1407
|
+
gap: var(--sf-space-s);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1269
1410
|
.sf-popover,
|
|
1270
1411
|
.sf-menu-popover,
|
|
1271
1412
|
.sf-tooltip,
|
|
@@ -1291,6 +1432,7 @@
|
|
|
1291
1432
|
:where(.sf-popover, .sf-menu-popover, .sf-tooltip, .sf-toast, .sf-banner, .sf-drawer):popover-open {
|
|
1292
1433
|
display: grid;
|
|
1293
1434
|
gap: var(--sf-space-xs);
|
|
1435
|
+
z-index: var(--sf-z-overlay);
|
|
1294
1436
|
}
|
|
1295
1437
|
|
|
1296
1438
|
.sf-popover {
|
|
@@ -1319,6 +1461,16 @@
|
|
|
1319
1461
|
background: var(--sf-colour-surface-alt);
|
|
1320
1462
|
}
|
|
1321
1463
|
|
|
1464
|
+
.sf-menu :where(a, button)[data-variant="danger"],
|
|
1465
|
+
.sf-menu-popover :where(a, button)[data-variant="danger"] {
|
|
1466
|
+
color: var(--sf-colour-danger);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
.sf-menu :where(a, button)[data-variant="danger"]:hover,
|
|
1470
|
+
.sf-menu-popover :where(a, button)[data-variant="danger"]:hover {
|
|
1471
|
+
background: var(--sf-colour-danger-soft);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1322
1474
|
.sf-tooltip {
|
|
1323
1475
|
border-radius: var(--sf-radius-md);
|
|
1324
1476
|
box-shadow: var(--sf-shadow-md);
|
|
@@ -1345,6 +1497,16 @@
|
|
|
1345
1497
|
top: var(--sf-space-s);
|
|
1346
1498
|
}
|
|
1347
1499
|
|
|
1500
|
+
.sf-toast-stack {
|
|
1501
|
+
display: grid;
|
|
1502
|
+
gap: var(--sf-space-xs);
|
|
1503
|
+
inline-size: min(100% - (var(--sf-gutter) * 2), var(--sf-toast-width, 24rem));
|
|
1504
|
+
inset-block-start: var(--sf-space-s);
|
|
1505
|
+
inset-inline-end: var(--sf-space-s);
|
|
1506
|
+
position: fixed;
|
|
1507
|
+
z-index: var(--sf-z-toast);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1348
1510
|
.sf-banner {
|
|
1349
1511
|
block-size: auto;
|
|
1350
1512
|
border-radius: var(--sf-radius-lg);
|
|
@@ -1457,15 +1619,23 @@
|
|
|
1457
1619
|
}
|
|
1458
1620
|
|
|
1459
1621
|
.sf-tab {
|
|
1622
|
+
align-items: center;
|
|
1460
1623
|
border-block-end: 0.125rem solid transparent;
|
|
1624
|
+
border-radius: var(--sf-radius-full);
|
|
1461
1625
|
color: var(--sf-colour-muted);
|
|
1462
1626
|
display: inline-flex;
|
|
1627
|
+
gap: var(--sf-space-2xs);
|
|
1463
1628
|
font-weight: 800;
|
|
1464
1629
|
padding-block: var(--sf-space-xs);
|
|
1465
1630
|
padding-inline: var(--sf-space-s);
|
|
1466
1631
|
text-decoration: none;
|
|
1467
1632
|
}
|
|
1468
1633
|
|
|
1634
|
+
.sf-tab[data-variant="pill"] {
|
|
1635
|
+
border: 1px solid transparent;
|
|
1636
|
+
padding-block: var(--sf-space-2xs);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1469
1639
|
.sf-tab:hover,
|
|
1470
1640
|
.sf-tab[aria-selected="true"],
|
|
1471
1641
|
.sf-tab[data-state="active"] {
|
|
@@ -1473,6 +1643,24 @@
|
|
|
1473
1643
|
color: var(--sf-colour-foreground);
|
|
1474
1644
|
}
|
|
1475
1645
|
|
|
1646
|
+
.sf-tab[data-variant="pill"]:hover,
|
|
1647
|
+
.sf-tab[data-variant="pill"][aria-selected="true"],
|
|
1648
|
+
.sf-tab[data-variant="pill"][data-state="active"] {
|
|
1649
|
+
background: var(--sf-colour-primary-soft);
|
|
1650
|
+
border-color: var(--sf-colour-primary-soft-border);
|
|
1651
|
+
color: var(--sf-colour-primary);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
.sf-tab__count {
|
|
1655
|
+
background: var(--sf-colour-surface-alt);
|
|
1656
|
+
border: 1px solid var(--sf-colour-border);
|
|
1657
|
+
border-radius: var(--sf-radius-full);
|
|
1658
|
+
color: var(--sf-colour-muted);
|
|
1659
|
+
font-size: var(--sf-step--2);
|
|
1660
|
+
line-height: 1;
|
|
1661
|
+
padding: .25em .55em;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1476
1664
|
.sf-tab-panel {
|
|
1477
1665
|
background: var(--sf-colour-surface);
|
|
1478
1666
|
border: 1px solid var(--sf-colour-border);
|
package/docs/api-contract.md
CHANGED
|
@@ -13,9 +13,9 @@ These are intended for application code and examples.
|
|
|
13
13
|
| Imports | `@syncedco/flow/styles.css`, `tokens.css`, `reset.css`, `base.css`, `defaults.css`, `layout.css`, `components.css`, `utilities.css` |
|
|
14
14
|
| Tokens | Unlayered `--sf-*` custom properties emitted by `tokens.css` |
|
|
15
15
|
| Theme config | `theme.fonts`, `theme.colours`, `theme.darkColours`, `theme.radii`, `theme.layout`, `theme.components` |
|
|
16
|
-
| Layout classes | `sf-container`, `sf-section`, `sf-stack`, `sf-flow`, `sf-cluster`, `sf-repel`, `sf-toolbar`, `sf-app-shell`, `sf-app-sidebar`, `sf-app-main`, `sf-auto-grid`, `sf-switcher`, `sf-sidebar`, `sf-split`, `sf-frame`, `sf-cover`, `sf-metric-grid`, `sf-pipeline` |
|
|
17
|
-
| Components | `sf-button`, `sf-icon`, `sf-icon-button`, `sf-avatar`, `sf-chart`, `sf-meter`, `sf-progress`, `sf-skeleton`, `sf-filter-bar`, `sf-data-table`, `sf-status`, `sf-data-list`, `sf-empty-state`, `sf-settings-section`, `sf-detail-panel`, `sf-card`, `sf-surface`, `sf-hero`, `sf-nav`, `sf-form`, `sf-field`, `sf-input`, `sf-select`, `sf-textarea`, `sf-check`, `sf-alert`, `sf-badge`, `sf-section-header`, `sf-kicker` |
|
|
18
|
-
| Native components | `sf-dialog`, `sf-popover`, `sf-tooltip`, `sf-drawer`, `sf-drawer--stack`, `sf-disclosure`, `sf-accordion`, `sf-tabs`, `sf-menu`, `sf-breadcrumb`, `sf-pagination` |
|
|
16
|
+
| Layout classes | `sf-container`, `sf-section`, `sf-stack`, `sf-flow`, `sf-cluster`, `sf-repel`, `sf-toolbar`, `sf-app-shell`, `sf-app-header`, `sf-app-sidebar`, `sf-app-backdrop`, `sf-app-main`, `sf-auto-grid`, `sf-switcher`, `sf-sidebar`, `sf-split`, `sf-frame`, `sf-cover`, `sf-metric-grid`, `sf-pipeline` |
|
|
17
|
+
| Components | `sf-button`, `sf-icon`, `sf-icon-button`, `sf-avatar`, `sf-chart`, `sf-meter`, `sf-progress`, `sf-skeleton`, `sf-filter-bar`, `sf-search`, `sf-data-table`, `sf-table-sort`, `sf-status`, `sf-data-list`, `sf-empty-state`, `sf-settings-section`, `sf-detail-panel`, `sf-card`, `sf-surface`, `sf-hero`, `sf-nav`, `sf-form`, `sf-field`, `sf-input`, `sf-select`, `sf-textarea`, `sf-check`, `sf-alert`, `sf-badge`, `sf-section-header`, `sf-kicker` |
|
|
18
|
+
| Native components | `sf-dialog`, `sf-popover`, `sf-tooltip`, `sf-drawer`, `sf-drawer--stack`, `sf-disclosure`, `sf-accordion`, `sf-tabs`, `sf-tab__count`, `sf-menu`, `sf-breadcrumb`, `sf-pagination`, `sf-toast-stack` |
|
|
19
19
|
| Website patterns | `sf-logo-cloud`, `sf-feature`, `sf-stats`, `sf-testimonial`, `sf-pricing-grid`, `sf-price-card`, `sf-faq`, `sf-cta`, `sf-footer` |
|
|
20
20
|
| Utilities | `sf-prose`, `sf-link`, `sf-link-subtle`, `sf-link-plain`, `sf-list-*`, `sf-push-*`, `sf-focus-ring`, `sf-touch-target`, `sf-skip-link`, `sr-only`, `not-sr-only` |
|
|
21
21
|
| CLI | `synced-flow init`, `agents install`, `agents status`, `skill`, `add defaults`, `build`, `watch`, `lint`, `doctor`, `tokens`, `catalog`, `suggest`, `pattern`, `recipe`, `theme init`, `theme validate` |
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Synced Flow Audit — June 2026 (v0.3.0)
|
|
2
|
+
|
|
3
|
+
Independent audit of `@syncedco/flow` at v0.3.0 (`a8a1f68`), run across three
|
|
4
|
+
passes: product coherence (claims vs shipped), CSS quality, and tooling/release
|
|
5
|
+
engineering. Findings are grounded in the source and in real consumer feedback
|
|
6
|
+
from the Synced CRM (`synced-crm`, the first production app consumer).
|
|
7
|
+
|
|
8
|
+
## Verdict
|
|
9
|
+
|
|
10
|
+
Flow delivers its **original model (websites/agency) almost completely** and
|
|
11
|
+
the **engineering discipline behind it impressively**. It falls short in three
|
|
12
|
+
places: **app-tier depth**, **advanced theming scope**, and a few product
|
|
13
|
+
positioning/docs gaps. It is a real, well-built system, and the positioning now
|
|
14
|
+
fits better as AI-friendly / agent-ready.
|
|
15
|
+
|
|
16
|
+
| Dimension | Rating | Summary |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| Fluid foundation (tokens, scales, layout) | ✅ Excellent | Genuine Utopia-style fluidity; modern CSS (layers, oklch, logical props, container queries); clean layer hygiene |
|
|
19
|
+
| Website/agency coverage | ✅ ~90% | Everything claimed ships; agency-ready without custom CSS |
|
|
20
|
+
| Tooling & release engineering | ✅ Excellent | 14 tested CLI commands, guardrails (size budgets, zero-dep, px policy), 40/40 tests, automated tag-driven publishing |
|
|
21
|
+
| App/SaaS tier (0.2–0.3) | ⚠️ ~60% | Real scaffolding (proven by the CRM) but thin; consumers carry 20–40% custom CSS |
|
|
22
|
+
| Dark mode | ✅ Patched after audit | `.sf-theme-dark` now overrides primary/status/soft/ring/glow tokens rather than inheriting light-tuned values |
|
|
23
|
+
| Theming config | ✅ Documented boundary | Config handles global light/dark token themes; advanced per-surface/multi-variant theming belongs in project CSS token scopes |
|
|
24
|
+
| AI positioning | ✅ Reframed | Positioning now uses AI-friendly / agent-ready language for the shipped CLI, skill, and agent guidance |
|
|
25
|
+
| Docs | ⚠️ Improving | Website docs thorough; CHANGELOG and theming-scope docs added; app-tier docs can still mature |
|
|
26
|
+
|
|
27
|
+
## Confirmed bugs (found by the CRM, verified in source)
|
|
28
|
+
|
|
29
|
+
1. **Drawer can't scroll** — fixed after audit by adding `block-size: 100dvh`
|
|
30
|
+
to `.sf-app-shell[data-mobile="drawer"] .sf-app-sidebar`, giving the
|
|
31
|
+
existing `overflow: auto` a real viewport-height scroll container.
|
|
32
|
+
2. **`.sf-tab` not vertically centred** — fixed after audit with
|
|
33
|
+
`align-items: center`.
|
|
34
|
+
3. **`.sf-filter-bar` `align-items: end`** — fixed after audit with
|
|
35
|
+
`align-items: var(--sf-filter-bar-align, center)`, preserving a project
|
|
36
|
+
escape hatch without adding a new component variant.
|
|
37
|
+
4. **`.sf-status` dot is mandatory** — fixed after audit with
|
|
38
|
+
`data-dot="false"` and `sf-status--label` opt-outs.
|
|
39
|
+
|
|
40
|
+
## Higher-severity findings the CRM did NOT hit
|
|
41
|
+
|
|
42
|
+
These mattered because no consumer had exercised them yet. They are now patched
|
|
43
|
+
or documented, but should still be validated in the next real consumer.
|
|
44
|
+
|
|
45
|
+
5. **Dark mode token coverage is critically incomplete** — fixed after audit
|
|
46
|
+
for the shipped base theme by adding dark-mode primary, state, soft, ring,
|
|
47
|
+
and glow token overrides.
|
|
48
|
+
6. **Z-index scale is compressed and undocumented** — fixed after audit with
|
|
49
|
+
public `--sf-z-*` tokens wired into header, backdrop, drawer, overlay, toast,
|
|
50
|
+
sticky, and skip-link layers.
|
|
51
|
+
7. **Missing `prefers-reduced-motion` guards** — fixed after audit for
|
|
52
|
+
`.sf-button:hover`, `.sf-card--interactive:hover`, and the marquee track.
|
|
53
|
+
|
|
54
|
+
## Theming model limitation (real, undocumented)
|
|
55
|
+
|
|
56
|
+
Config supports global light + `darkColours` + per-preset overrides. It does
|
|
57
|
+
not try to express per-surface / multi-variant theming (e.g. `[data-theme]` ×
|
|
58
|
+
`[data-surface]` matrices, per-tenant white-label, density-by-context). That is
|
|
59
|
+
a legitimate design choice for Synced Flow's scope, and `config-reference.md`
|
|
60
|
+
now documents the hand-written token-scope pattern for advanced cases.
|
|
61
|
+
|
|
62
|
+
## AI-Friendly Positioning
|
|
63
|
+
|
|
64
|
+
What ships is real and good: `catalog --json`, `suggest`, `pattern`, `recipe`,
|
|
65
|
+
`lint`, `doctor`, a packaged skill, and `agents install` for 8 targets. The
|
|
66
|
+
project now describes this as **AI-friendly** and agent-readable tooling rather
|
|
67
|
+
than implying runtime AI integration.
|
|
68
|
+
|
|
69
|
+
## App-tier coverage gaps (from real CRM adoption)
|
|
70
|
+
|
|
71
|
+
Shipped and adopted: app-shell, data-table, empty-state, filter-bar, status
|
|
72
|
+
chips, tabs, toast skin, form field shells. Still missing for production app
|
|
73
|
+
work (CRM kept custom or deferred): combobox/searchable select, pagination
|
|
74
|
+
depth (page size, jump-to-page), modal-with-form as a primitive (not just a
|
|
75
|
+
pattern), bulk-actions toolbar, multi-step/wizard, skeleton loading compounds,
|
|
76
|
+
sticky-header + horizontal-scroll tables. Kanban/timesheet/invoice-doc are
|
|
77
|
+
deliberately consumer-specific and out of scope.
|
|
78
|
+
|
|
79
|
+
## Strengths (keep doing)
|
|
80
|
+
|
|
81
|
+
- Real fluidity; modern CSS; excellent cascade-layer hygiene.
|
|
82
|
+
- Tooling/release engineering is the strongest area: guardrails enforce
|
|
83
|
+
zero-dependency, per-layer gzip size budgets, layer order, and a raw-px
|
|
84
|
+
policy; 40/40 tests pass; tag-driven OIDC publishing; complete exports map.
|
|
85
|
+
- CLI catalog currently in sync with shipped CSS (verified) — though by hand.
|
|
86
|
+
- AI agent guidance (skill + `agents install`) is current with 0.3.0 primitives.
|
|
87
|
+
|
|
88
|
+
## Recommended roadmap
|
|
89
|
+
|
|
90
|
+
**0.3.1 — bug patch (small, soon):**
|
|
91
|
+
1. Drawer viewport scroll container. ✅
|
|
92
|
+
2. `.sf-tab` `align-items: center`. ✅
|
|
93
|
+
3. `.sf-filter-bar` tokenized alignment with centered default. ✅
|
|
94
|
+
4. `.sf-status` dot opt-out. ✅
|
|
95
|
+
5. `prefers-reduced-motion` guards on hover transforms + marquee. ✅
|
|
96
|
+
|
|
97
|
+
**0.4.0 — structural gaps:**
|
|
98
|
+
6. Continue dark-mode validation with a real consumer using Flow's built-in
|
|
99
|
+
theme selectors.
|
|
100
|
+
7. Begin app-tier depth: combobox, pagination, modal-with-form, bulk-actions —
|
|
101
|
+
prioritised by what the CRM had to keep custom.
|
|
102
|
+
|
|
103
|
+
**Docs / positioning / CI (cheap, high-trust):**
|
|
104
|
+
8. CI: smoke-build the examples; add a test asserting the CLI catalog matches
|
|
105
|
+
the shipped CSS (in sync today only by hand); consider a CSS parse/lint and
|
|
106
|
+
a small visual-regression pass.
|
|
107
|
+
|
|
108
|
+
## Meta-observation
|
|
109
|
+
|
|
110
|
+
The four bugs the CRM surfaced are exactly the class only a real consumer finds
|
|
111
|
+
— the proving-ground loop is working as intended. The most useful next
|
|
112
|
+
validation step is getting a second consumer, or the CRM itself, onto Flow's
|
|
113
|
+
built-in dark mode and app primitives before 1.0.
|
package/docs/config-reference.md
CHANGED
|
@@ -79,3 +79,30 @@ theme: {
|
|
|
79
79
|
},
|
|
80
80
|
}
|
|
81
81
|
```
|
|
82
|
+
|
|
83
|
+
## Theme Scope
|
|
84
|
+
|
|
85
|
+
The config theme model is intentionally global. Use `theme.colours` for the
|
|
86
|
+
default token set and `theme.darkColours` for `.sf-theme-dark` /
|
|
87
|
+
`[data-sf-theme="dark"]` overrides. This covers the common site or app case:
|
|
88
|
+
one brand token set, with an optional dark-mode token set.
|
|
89
|
+
|
|
90
|
+
Synced Flow does not try to encode every theme matrix in config. If a project
|
|
91
|
+
needs per-tenant branding, `[data-surface]` variants, density-by-context, or
|
|
92
|
+
other multi-axis theme rules, keep the reusable defaults in config and write
|
|
93
|
+
the extra token scopes in project CSS:
|
|
94
|
+
|
|
95
|
+
```css
|
|
96
|
+
[data-tenant="acme"] {
|
|
97
|
+
--sf-colour-primary: oklch(62% 0.19 252);
|
|
98
|
+
--sf-colour-primary-hover: oklch(56% 0.19 252);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
[data-surface="inset"] {
|
|
102
|
+
--sf-colour-surface: var(--sf-colour-surface-inset);
|
|
103
|
+
--sf-card-padding: var(--sf-space-s);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Use this pattern for deliberate product theming. Avoid scattering one-off
|
|
108
|
+
colour values through page components when a value can live in a token scope.
|
package/docs/patterns.md
CHANGED
|
@@ -18,6 +18,13 @@ pnpm exec synced-flow pattern scroll-viewport-sections --json
|
|
|
18
18
|
| `scroll-viewport-with-spy` | Scroll snap panels with optional IntersectionObserver active-state notes. |
|
|
19
19
|
| `native-dialog-react` | Native `<dialog>` markup plus React/Next fallback shape. |
|
|
20
20
|
| `popover-drawer-layout` | Popover-backed drawer or filter panel with `sf-drawer--stack`. |
|
|
21
|
+
| `app-shell-layout` | Fixed header, sidebar, collapsed state, and mobile drawer app shell. |
|
|
22
|
+
| `row-action-menu` | Native popover row actions with destructive menu item styling. |
|
|
23
|
+
| `tabs-with-counts` | Pill tabs with compact count chips. |
|
|
24
|
+
| `data-table-actions` | Sortable header controls, clickable rows, and responsive card fallback. |
|
|
25
|
+
| `search-field` | Labelled native search input with icon shell and compact density. |
|
|
26
|
+
| `modal-form` | Native dialog layout for form body and footer actions. |
|
|
27
|
+
| `toast-stack` | Fixed app notification stack layout. |
|
|
21
28
|
| `filter-toolbar` | Search, filters, and actions above app lists and tables. |
|
|
22
29
|
| `date-filter-form` | Native date/time inputs inside filter forms. |
|
|
23
30
|
| `active-filter-tags` | Visible selected filter tags with remove and clear actions. |
|
|
@@ -14,7 +14,10 @@ Use the `--sf-*` variables as the stable CSS foundation.
|
|
|
14
14
|
| Space | `--sf-space-s`, `--sf-space-m-l`, `--sf-space-xl-2xl` | Fluid padding, margin, and gaps. |
|
|
15
15
|
| Radius | `--sf-radius-control`, `--sf-radius-panel`, `--sf-radius-full` | Controls, panels, pills. |
|
|
16
16
|
| Colour | `--sf-colour-background`, `--sf-colour-surface`, `--sf-colour-primary` | Semantic UI colour roles. |
|
|
17
|
+
| App colour | `--sf-colour-surface-hover`, `--sf-colour-surface-raised-2`, `--sf-colour-border-subtle`, `--sf-colour-backdrop` | App surfaces, hover states, subtle dividers, and overlays. |
|
|
18
|
+
| Categorical colour | `--sf-colour-categorical-1` ... `--sf-colour-categorical-6` | Charts, stages, labels, and recurring data accents. |
|
|
17
19
|
| State | `--sf-colour-success`, `--sf-colour-warning`, `--sf-colour-danger`, `--sf-colour-info` | Feedback and notices. |
|
|
20
|
+
| Z-index | `--sf-z-header`, `--sf-z-backdrop`, `--sf-z-drawer`, `--sf-z-overlay`, `--sf-z-toast` | Shared stacking slots for app chrome, drawers, popovers, and feedback. |
|
|
18
21
|
| Motion | `--sf-duration-fast`, `--sf-duration-normal`, `--sf-ease-standard` | Consistent transitions. |
|
|
19
22
|
| Component | `--sf-button-*`, `--sf-card-*`, `--sf-input-*`, `--sf-alert-*` | Reusable component defaults. |
|
|
20
23
|
| Accessibility | `:focus-visible`, `:target`, `[aria-invalid]`, `[aria-current]`, `[aria-expanded]`, `[aria-selected]`, `[aria-busy]`, `[aria-disabled]` | Visible native and ARIA states. |
|
|
@@ -36,7 +39,7 @@ These classes are the first choice for page structure.
|
|
|
36
39
|
| `sf-cluster` | Wrapping inline groups such as actions or tags. |
|
|
37
40
|
| `sf-repel` | Space-between layout that wraps safely. |
|
|
38
41
|
| `sf-toolbar` | Dense app/page toolbar that wraps actions safely. |
|
|
39
|
-
| `sf-app-shell`, `sf-app-sidebar`, `sf-app-sidebar__brand`, `sf-app-main` | Product app shell with
|
|
42
|
+
| `sf-app-shell`, `sf-app-header`, `sf-app-sidebar`, `sf-app-sidebar__brand`, `sf-app-backdrop`, `sf-app-main` | Product app shell with optional fixed header, collapsed sidebar, mobile drawer, and main workspace. |
|
|
40
43
|
| `sf-split` | Responsive two-column layout. |
|
|
41
44
|
| `sf-split--reverse` | Reverses split layout visual order. |
|
|
42
45
|
| `sf-auto-grid`, `sf-panel-grid`, `sf-metric-grid`, `sf-pipeline` | Responsive card, metric, and operational grids. |
|
|
@@ -62,16 +65,16 @@ These cover the common UI elements needed for a simple site.
|
|
|
62
65
|
| `sf-nav`, `sf-nav__list`, `sf-nav__link` | Navigation basics. |
|
|
63
66
|
| `sf-nav--mobile`, `sf-menu`, `sf-breadcrumb`, `sf-pagination` | Mobile, menu, breadcrumb, and paginated navigation. |
|
|
64
67
|
| `sf-dialog`, `sf-dialog__header`, `sf-dialog__body`, `sf-dialog__footer` | Native `<dialog>` styling. |
|
|
65
|
-
| `sf-popover`, `sf-tooltip`, `sf-tooltip-trigger`, `sf-menu-popover`, `sf-toast`, `sf-banner`, `sf-drawer` | Popover-backed native overlays. |
|
|
68
|
+
| `sf-popover`, `sf-tooltip`, `sf-tooltip-trigger`, `sf-menu-popover`, `sf-toast`, `sf-toast-stack`, `sf-banner`, `sf-drawer` | Popover-backed native overlays and app feedback stacks. |
|
|
66
69
|
| `sf-disclosure`, `sf-accordion` | Native `details`/`summary` disclosure patterns. |
|
|
67
|
-
| `sf-tabs`, `sf-tab-list`, `sf-tab`, `sf-tab-panel` | HTML/CSS-first tab styling. |
|
|
70
|
+
| `sf-tabs`, `sf-tab-list`, `sf-tab`, `sf-tab__count`, `sf-tab-panel` | HTML/CSS-first tab styling, including counted pill tabs. |
|
|
68
71
|
| `sf-form`, `sf-fieldset`, `sf-field`, `sf-label`, `sf-help`, `sf-error` | Form structure and messaging. |
|
|
69
|
-
| `sf-input`, `sf-select`, `sf-textarea`, `sf-check` | Form controls. |
|
|
72
|
+
| `sf-input`, `sf-select`, `sf-textarea`, `sf-search`, `sf-check` | Form controls, including search input shell. |
|
|
70
73
|
| `sf-alert`, `sf-alert--info`, `sf-alert--success`, `sf-alert--warning`, `sf-alert--danger`, `sf-alert__title` | Notices and feedback. |
|
|
71
74
|
| `sf-section-header`, `sf-kicker`, `sf-badge`, `sf-avatar` | Common marketing/content and account patterns. |
|
|
72
75
|
| `sf-chart`, `sf-chart__plot`, `sf-chart__svg`, `sf-chart__legend`, `sf-meter-list`, `sf-meter` | Lightweight chart shells for app-owned SVG charts and native meter bars. |
|
|
73
76
|
| `sf-progress`, `sf-skeleton` | Native progress bars and CSS-only loading placeholders for async app states. |
|
|
74
|
-
| `sf-filter-bar`, `sf-data-table`, `sf-status`, `sf-data-list`, `sf-empty-state`, `sf-settings-section`, `sf-detail-panel` | Lean application primitives for filters, operational data, status, empty states, and settings/detail screens. |
|
|
77
|
+
| `sf-filter-bar`, `sf-data-table`, `sf-table-sort`, `sf-status`, `sf-data-list`, `sf-empty-state`, `sf-settings-section`, `sf-detail-panel` | Lean application primitives for filters, operational data, status, empty states, and settings/detail screens. |
|
|
75
78
|
|
|
76
79
|
## Utility Classes
|
|
77
80
|
|
package/docs/tokens.md
CHANGED
|
@@ -75,6 +75,7 @@ The generated core includes enough semantic variables for common website UI:
|
|
|
75
75
|
|
|
76
76
|
- surfaces: `--sf-colour-background`, `--sf-colour-surface`,
|
|
77
77
|
`--sf-colour-surface-alt`, `--sf-colour-surface-raised`,
|
|
78
|
+
`--sf-colour-surface-raised-2`, `--sf-colour-surface-hover`,
|
|
78
79
|
`--sf-colour-surface-inset`
|
|
79
80
|
- text and links: `--sf-colour-foreground`, `--sf-colour-muted`,
|
|
80
81
|
`--sf-colour-subtle`, `--sf-colour-link`, `--sf-colour-link-hover`
|
|
@@ -82,8 +83,13 @@ The generated core includes enough semantic variables for common website UI:
|
|
|
82
83
|
`--sf-colour-primary-foreground`, `--sf-colour-primary-soft`
|
|
83
84
|
- state feedback: `--sf-colour-success`, `--sf-colour-warning`,
|
|
84
85
|
`--sf-colour-danger`, `--sf-colour-info`, plus matching soft variants
|
|
85
|
-
- structure: `--sf-colour-border`, `--sf-colour-border
|
|
86
|
+
- structure: `--sf-colour-border-subtle`, `--sf-colour-border`,
|
|
87
|
+
`--sf-colour-border-strong`, `--sf-colour-backdrop`,
|
|
86
88
|
`--sf-radius-control`, `--sf-radius-panel`, `--sf-shadow-*`
|
|
89
|
+
- stacking: `--sf-z-sticky`, `--sf-z-header`, `--sf-z-backdrop`,
|
|
90
|
+
`--sf-z-drawer`, `--sf-z-overlay`, `--sf-z-toast`, `--sf-z-skip-link`
|
|
91
|
+
- data accents: `--sf-colour-categorical-1` through
|
|
92
|
+
`--sf-colour-categorical-6`
|
|
87
93
|
|
|
88
94
|
For class-level usage, see [System primitives](system-primitives.md).
|
|
89
95
|
|
package/docs/why-synced-flow.md
CHANGED
|
@@ -22,7 +22,7 @@ Synced Flow is designed to reduce the number of styling decisions made from scra
|
|
|
22
22
|
|
|
23
23
|
Synced Flow is not trying to be a Tailwind clone, a Bootstrap replacement, or a React component library.
|
|
24
24
|
|
|
25
|
-
It is an AI-
|
|
25
|
+
It is an AI-friendly fluid CSS design system for brand-consistent websites and agency builds.
|
|
26
26
|
|
|
27
27
|
Use it when you want:
|
|
28
28
|
|
package/layout.css
CHANGED
|
@@ -95,11 +95,47 @@
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
.sf-app-shell {
|
|
98
|
+
--sf-app-header-height: 4rem;
|
|
99
|
+
--sf-app-sidebar-collapsed-width: 4.75rem;
|
|
98
100
|
background: var(--sf-colour-background);
|
|
99
101
|
color: var(--sf-colour-foreground);
|
|
100
102
|
display: grid;
|
|
101
103
|
grid-template-columns: minmax(14rem, var(--sf-app-sidebar-width, 16rem)) minmax(0, 1fr);
|
|
102
104
|
min-block-size: 100dvh;
|
|
105
|
+
position: relative;
|
|
106
|
+
transition: grid-template-columns var(--sf-duration-normal) var(--sf-ease-standard);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.sf-app-header {
|
|
110
|
+
align-items: center;
|
|
111
|
+
background: var(--sf-colour-surface);
|
|
112
|
+
border-block-end: 1px solid var(--sf-colour-border);
|
|
113
|
+
display: flex;
|
|
114
|
+
gap: var(--sf-space-s);
|
|
115
|
+
grid-column: 1 / -1;
|
|
116
|
+
justify-content: space-between;
|
|
117
|
+
min-block-size: var(--sf-app-header-height);
|
|
118
|
+
padding-inline: var(--sf-space-s-m);
|
|
119
|
+
z-index: var(--sf-z-header);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.sf-app-shell[data-layout~="fixed-header"] {
|
|
123
|
+
grid-template-rows: var(--sf-app-header-height) minmax(0, 1fr);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.sf-app-shell[data-layout~="fixed-header"] .sf-app-header {
|
|
127
|
+
inset-block-start: 0;
|
|
128
|
+
position: sticky;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.sf-app-shell[data-layout~="fixed-header"] .sf-app-sidebar {
|
|
132
|
+
inset-block-start: var(--sf-app-header-height);
|
|
133
|
+
max-block-size: calc(100dvh - var(--sf-app-header-height));
|
|
134
|
+
min-block-size: calc(100dvh - var(--sf-app-header-height));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.sf-app-shell[data-collapsed="true"] {
|
|
138
|
+
grid-template-columns: var(--sf-app-sidebar-collapsed-width) minmax(0, 1fr);
|
|
103
139
|
}
|
|
104
140
|
|
|
105
141
|
.sf-app-sidebar {
|
|
@@ -115,6 +151,7 @@
|
|
|
115
151
|
padding: var(--sf-space-s);
|
|
116
152
|
position: sticky;
|
|
117
153
|
inset-block-start: 0;
|
|
154
|
+
transition: transform var(--sf-duration-normal) var(--sf-ease-standard), inline-size var(--sf-duration-normal) var(--sf-ease-standard);
|
|
118
155
|
}
|
|
119
156
|
|
|
120
157
|
.sf-app-sidebar__brand {
|
|
@@ -134,6 +171,59 @@
|
|
|
134
171
|
padding: var(--sf-app-main-padding, var(--sf-space-m-l));
|
|
135
172
|
}
|
|
136
173
|
|
|
174
|
+
.sf-app-shell[data-collapsed="true"] .sf-app-sidebar {
|
|
175
|
+
overflow-x: hidden;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.sf-app-shell[data-collapsed="true"] .sf-app-sidebar__brand,
|
|
179
|
+
.sf-app-shell[data-collapsed="true"] .sf-app-sidebar .sf-nav__link {
|
|
180
|
+
justify-content: center;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.sf-app-shell[data-collapsed="true"] [data-hide-collapsed="true"] {
|
|
184
|
+
display: none;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.sf-app-backdrop {
|
|
188
|
+
display: none;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@media (max-width: 48rem) {
|
|
192
|
+
.sf-app-shell[data-mobile="drawer"] {
|
|
193
|
+
display: block;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.sf-app-shell[data-mobile="drawer"] .sf-app-header {
|
|
197
|
+
position: sticky;
|
|
198
|
+
inset-block-start: 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.sf-app-shell[data-mobile="drawer"] .sf-app-sidebar {
|
|
202
|
+
block-size: 100dvh;
|
|
203
|
+
box-shadow: var(--sf-shadow-xl);
|
|
204
|
+
inline-size: min(18rem, calc(100vw - var(--sf-space-l)));
|
|
205
|
+
inset-block: 0;
|
|
206
|
+
inset-inline-start: 0;
|
|
207
|
+
max-block-size: none;
|
|
208
|
+
min-block-size: 100dvh;
|
|
209
|
+
position: fixed;
|
|
210
|
+
transform: translateX(-100%);
|
|
211
|
+
z-index: var(--sf-z-drawer);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.sf-app-shell[data-mobile="drawer"][data-sidebar-open="true"] .sf-app-sidebar {
|
|
215
|
+
transform: translateX(0);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.sf-app-shell[data-mobile="drawer"][data-sidebar-open="true"] .sf-app-backdrop {
|
|
219
|
+
background: var(--sf-colour-backdrop);
|
|
220
|
+
display: block;
|
|
221
|
+
inset: 0;
|
|
222
|
+
position: fixed;
|
|
223
|
+
z-index: var(--sf-z-backdrop);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
137
227
|
.sf-grid {
|
|
138
228
|
display: grid;
|
|
139
229
|
gap: var(--sf-grid-gap, var(--sf-gutter));
|
|
@@ -274,7 +364,7 @@
|
|
|
274
364
|
.sf-sticky-top {
|
|
275
365
|
position: sticky;
|
|
276
366
|
inset-block-start: var(--sf-sticky-offset, 0);
|
|
277
|
-
z-index: var(--sf-sticky-z,
|
|
367
|
+
z-index: var(--sf-sticky-z, var(--sf-z-sticky));
|
|
278
368
|
}
|
|
279
369
|
|
|
280
370
|
.sf-media-object {
|