@arcforgelabs/flow 2026.10.38 → 2026.10.40

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 (47) hide show
  1. package/README.md +1 -1
  2. package/npm-shrinkwrap.json +2 -2
  3. package/package.json +4 -2
  4. package/server/api/custom-fields.js +26 -9
  5. package/server/api/invoicing.js +149 -51
  6. package/server/app.js +5 -2
  7. package/server/core/alerts.js +1 -1
  8. package/server/core/automations.js +1 -1
  9. package/server/core/custom-fields.js +74 -6
  10. package/server/core/ledger.js +29 -7
  11. package/server/core/price-list.js +27 -2
  12. package/server/core/read-before-act.js +3 -2
  13. package/server/core/sending.js +36 -18
  14. package/server/db/doctor.js +18 -19
  15. package/server/db/index.js +74 -3
  16. package/server/db/migrate-all.js +3 -4
  17. package/server/db/migrations/0020_custom_field_colours.sql +21 -0
  18. package/server/db/migrations/meta/0020_snapshot.json +4377 -0
  19. package/server/db/migrations/meta/_journal.json +7 -0
  20. package/server/db/schema.js +14 -0
  21. package/server/integrations/call-guard.js +205 -15
  22. package/server/integrations/port.js +3 -2
  23. package/server/integrations/schema.js +9 -5
  24. package/server/integrations/seazona/settings.js +8 -4
  25. package/server/integrations/xero/drafts.js +4 -4
  26. package/server/integrations/xero/index.js +5 -1
  27. package/server/integrations/xero/migrations/0009_xero_sent_to_contact.sql +3 -0
  28. package/server/integrations/xero/migrations/meta/_journal.json +7 -0
  29. package/server/integrations/xero/mirror.js +4 -3
  30. package/server/integrations/xero/reconcile.js +25 -1
  31. package/server/integrations/xero/routes.js +24 -7
  32. package/server/integrations/xero/settings.js +2 -1
  33. package/server/ui/app.js +42 -6
  34. package/server/ui/custom-fields.js +252 -94
  35. package/server/ui/flow.css +185 -29
  36. package/server/ui/invoice-notes.js +2 -2
  37. package/server/ui/invoicing.js +92 -137
  38. package/server/ui/job-board.js +1 -1
  39. package/server/ui/job-change.js +23 -12
  40. package/server/ui/job-view.js +168 -33
  41. package/server/ui/jobs.js +316 -65
  42. package/server/ui/monitoring.js +13 -12
  43. package/server/ui/price-list.js +43 -86
  44. package/server/ui/price-match.js +93 -0
  45. package/server/ui/price-reconcile.js +357 -0
  46. package/server/ui/reconcile.js +1 -1
  47. package/server/ui/routes.js +1 -1
@@ -202,6 +202,10 @@ body:is([data-state="off"], [data-state="error"], [data-state="no-access"]) .fx-
202
202
  .fx-change__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 16px; }
203
203
  .fx-change__acts { display: flex; flex-wrap: wrap; gap: 8px; }
204
204
  .fx-change__note { font-size: 13px; color: var(--t-dim); }
205
+ .fx-change__recheck { width: 30px; }
206
+ .fx-change__recheck .fx-lucide { width: 14px; height: 14px; }
207
+ .fx-change__recheck.is-busy .fx-lucide { animation: fx-spin 1s linear infinite; }
208
+ @media (prefers-reduced-motion: reduce) { .fx-change__recheck.is-busy .fx-lucide { animation: none; opacity: .5; } }
205
209
  .fx-change__scroll { overflow-x: auto; }
206
210
  .fx-change__table { width: 100%; border-collapse: collapse; background: var(--t-paper); border: 1px solid var(--t-brd); font-size: 13.5px; }
207
211
  .fx-change__table th, .fx-change__table td { padding: 8px 12px; border-bottom: 1px solid var(--t-brd); text-align: left; vertical-align: top; }
@@ -235,10 +239,12 @@ a.fx-link:hover, a.fx-link:focus-visible { border-color: var(--t-strong); }
235
239
  .fx-link__dot[data-tone="success"] { background: var(--t-success); }
236
240
  .fx-link__dot[data-tone="warn"] { background: var(--t-warn); }
237
241
  .fx-link__dot[data-tone="danger"] { background: var(--t-danger); }
238
- .fx-linked { display: grid; }
239
- .fx-linked__row { display: flex; align-items: center; gap: 8px; white-space: nowrap; min-height: 44px; padding: 6px 0; border-top: 1px solid var(--fx-line-soft); }
242
+ .fx-linked { display: grid; grid-template-columns: minmax(0, 1fr); }
243
+ .fx-linked__row { display: flex; align-items: center; gap: 8px; min-width: 0; white-space: nowrap; min-height: 44px; padding: 6px 0; border-top: 1px solid var(--fx-line-soft); }
244
+ .fx-linked__row > * { flex: none; }
245
+ .fx-linked__row .fx-dim { flex: 1 1 auto; min-width: 0; white-space: normal; }
240
246
  .fx-linked__row:first-child { border-top: 0; }
241
- .fx-linked__ref { font-family: var(--t-data); font-variant-numeric: tabular-nums; color: var(--t-strong); }
247
+ .fx-linked__ref { flex: 0 1 auto !important; min-width: 0; overflow: hidden; text-overflow: ellipsis; font-family: var(--t-data); font-variant-numeric: tabular-nums; color: var(--t-strong); }
242
248
  .fx-linked__open { margin-left: auto; font-size: 12.5px; color: var(--t-dim); white-space: nowrap; }
243
249
  .fx-linked__open:hover { color: var(--t-strong); }
244
250
  .fx-tag {
@@ -417,11 +423,11 @@ a.fx-strong:hover { text-decoration: underline; }
417
423
  .fx-crumbs a:hover { color: var(--t-strong); text-decoration: underline; }
418
424
  .fx-invoice { display: grid; gap: 20px; }
419
425
  .fx-invoice__grid { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: 20px; align-items: start; }
420
- .fx-invoice__main, .fx-invoice__aside { display: grid; gap: 16px; }
426
+ .fx-invoice__main, .fx-invoice__aside { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; min-width: 0; }
421
427
  .fx-invoice__aside:empty { display: none; }
422
428
  .fx-invoice__grid:has(.fx-invoice__aside:empty) { grid-template-columns: minmax(0, 1fr); }
423
429
  .fx-invoice__head { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 16px; padding: 18px 20px; }
424
- .fx-side-card { display: grid; gap: 10px; padding: 16px; }
430
+ .fx-side-card { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; min-width: 0; padding: 16px; }
425
431
  .fx-facts { display: grid; grid-template-columns: max-content 1fr; gap: 6px 12px; margin: 0; font-size: 13.5px; }
426
432
  .fx-facts dt { color: var(--t-dim); }
427
433
  .fx-facts dd { margin: 0; color: var(--t-strong); overflow-wrap: anywhere; }
@@ -508,6 +514,52 @@ a.fx-strong:hover { text-decoration: underline; }
508
514
  .fx-pop__tick { display: inline-grid; place-items: center; flex: none; width: 16px; height: 16px; color: var(--t-accent); }
509
515
  .fx-pop__sep { height: 1px; margin: 6px 4px; background: var(--fx-line-soft); }
510
516
  .fx-pop__foot { display: flex; justify-content: flex-end; padding: 6px 4px 2px; border-top: 1px solid var(--fx-line-soft); margin-top: 6px; }
517
+ /* A chip with an × that clears it (as ClickUp's "Clear all filters"). */
518
+ .fx-chipset { display: inline-flex; align-items: stretch; }
519
+ .fx-chipset[hidden] { display: none; }
520
+ .fx-chipset > .fx-chip:not(:last-child):has(+ .fx-chip__clear:not([hidden])) { border-top-right-radius: 0; border-bottom-right-radius: 0; border-right-width: 0; padding-right: 8px; }
521
+ .fx-chip__clear {
522
+ display: inline-grid; place-items: center; width: 28px; padding: 0 6px 0 2px; border: 1px solid var(--fx-line); border-left: 0;
523
+ border-radius: 0 999px 999px 0; background: transparent; color: var(--t-dim); cursor: pointer;
524
+ }
525
+ .fx-chip__clear[hidden] { display: none; }
526
+ .fx-chip__clear .fx-lucide { width: 13px; height: 13px; }
527
+ .fx-chip__clear:hover { background: var(--fx-hover); color: var(--t-strong); }
528
+ .fx-chip__clear:focus-visible { outline: 2px solid var(--t-ring, var(--t-accent)); outline-offset: 2px; }
529
+ .fx-chipset--on .fx-chip__clear { border-color: var(--t-accent-brd, var(--t-accent)); background: var(--t-accent-bg); color: var(--t-accent); }
530
+ /* Group by, Sort and Filters: rows of selects with a bin at the end, as
531
+ ClickUp lays them out, in Flow's own look. */
532
+ .fx-pop.fx-pop--rows { width: max-content; min-width: 360px; max-width: min(680px, calc(100vw - 16px)); max-height: min(520px, calc(100vh - 32px)); padding: 12px; }
533
+ .fx-rows__title { margin: 0 0 10px; color: var(--t-strong); font: 600 14px/1.3 var(--t-body); }
534
+ .fx-rows { display: grid; gap: 8px; }
535
+ .fx-rows__row { display: grid; grid-template-columns: 78px minmax(130px, 1fr) minmax(120px, auto) minmax(150px, 1.2fr) 32px; gap: 8px; align-items: center; }
536
+ .fx-rows__row--sort { grid-template-columns: 78px minmax(150px, 1fr) minmax(140px, auto) 32px; }
537
+ .fx-rows__row--group { grid-template-columns: minmax(170px, 1fr) minmax(130px, auto) 32px; }
538
+ .fx-rows .fx-select, .fx-rows .fx-input { width: 100%; min-width: 0; min-height: 32px; font-size: 13px; }
539
+ .fx-rows__lead { color: var(--t-dim); font: 500 13px/1.2 var(--t-body); }
540
+ .fx-rows__value { overflow: hidden; text-align: left; text-overflow: ellipsis; white-space: nowrap; }
541
+ .fx-rows__gone { grid-column: span 3; color: var(--t-dim); font-size: 13px; font-style: italic; }
542
+ .fx-rows__bin {
543
+ display: inline-grid; place-items: center; width: 32px; height: 32px; border: 0; border-radius: var(--t-radius);
544
+ background: none; color: var(--t-dim); cursor: pointer;
545
+ }
546
+ .fx-rows__bin .fx-lucide { width: 15px; height: 15px; }
547
+ .fx-rows__bin:hover { background: var(--fx-hover); color: var(--t-danger); }
548
+ .fx-rows__bin:focus-visible { outline: 2px solid var(--t-ring, var(--t-accent)); outline-offset: 1px; }
549
+ .fx-rows__empty { margin: 2px 0 4px; color: var(--t-dim); font-size: 13px; }
550
+ .fx-rows__foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 12px; }
551
+ .fx-rows__add { display: inline-flex; align-items: center; gap: 6px; }
552
+ .fx-rows__add .fx-lucide { width: 14px; height: 14px; }
553
+ .fx-pop.fx-pop--choices { min-width: 200px; max-width: 300px; padding: 6px; }
554
+ .fx-pop--choices .fx-input { min-height: 32px; margin-bottom: 4px; font-size: 13px; }
555
+ .fx-pop__end { display: inline-grid; margin-left: auto; color: var(--t-accent); }
556
+ @media (max-width: 640px) {
557
+ .fx-pop.fx-pop--rows { min-width: 0; width: calc(100vw - 16px); }
558
+ .fx-rows__row, .fx-rows__row--sort { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 32px; }
559
+ .fx-rows__row > .fx-rows__lead, .fx-rows__row > .fx-select:first-child { grid-column: 1 / -1; }
560
+ .fx-rows__row > .fx-rows__value { grid-column: 1 / 3; }
561
+ .fx-rows__row > .fx-rows__value--none { display: none; }
562
+ }
511
563
  .fx-table__group td { padding: 14px 16px 8px; background: var(--t-s1); }
512
564
  .fx-table tbody tr.fx-table__group:hover td { background: var(--t-s1); }
513
565
  .fx-table__group-row { display: flex; align-items: center; gap: 12px; }
@@ -528,6 +580,21 @@ a.fx-strong:hover { text-decoration: underline; }
528
580
  .fx-th:hover, .fx-th:focus-visible { background: var(--fx-hover); color: var(--t-strong); outline: none; }
529
581
  .fx-th-add { width: 36px; text-align: right; }
530
582
  .fx-th--add { font-size: 16px; line-height: 1; }
583
+ .fx-th--fixed, .fx-th--fixed:hover { cursor: default; background: none; color: inherit; }
584
+ /* A column head's sort button (as ClickUp's): shown on hover, and always
585
+ while the column sorts, as a small filled circle with the arrow. */
586
+ .fx-th-cell { display: inline-flex; align-items: center; gap: 6px; }
587
+ .fx-th-sort {
588
+ display: inline-grid; grid-auto-flow: column; place-items: center; gap: 1px; min-width: 22px; height: 22px; padding: 0 4px;
589
+ border: 0; border-radius: 999px; background: none; color: var(--t-dim); cursor: pointer; opacity: 0;
590
+ }
591
+ th:hover .fx-th-sort, .fx-th-sort:focus-visible, .fx-th-sort--on { opacity: 1; }
592
+ .fx-th-sort:hover { background: var(--fx-hover); color: var(--t-strong); }
593
+ .fx-th-sort:focus-visible { outline: 2px solid var(--t-ring, var(--t-accent)); outline-offset: 1px; }
594
+ .fx-th-sort--on, .fx-th-sort--on:hover { background: var(--t-accent-bg); color: var(--t-accent); }
595
+ .fx-th-sort .fx-lucide { width: 13px; height: 13px; }
596
+ .fx-th-sort__n { font: 700 10px/1 var(--t-body); }
597
+ @media (hover: none) { .fx-th-sort { opacity: 1; } }
531
598
  .fx-avatar--sm { width: 24px; height: 24px; font-size: 10px; }
532
599
  .fx-c-date time { font-variant-numeric: tabular-nums; }
533
600
  .fx-stage[data-rank="plain"] { --stage: var(--t-dim); }
@@ -958,43 +1025,75 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
958
1025
  .fx-set__control .fx-input[type="number"] { width: 96px; }
959
1026
  .fx-set__control .fx-select { min-width: 220px; }
960
1027
  .fx-set + .fx-set { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--fx-line); }
961
- /* Price list (price-list.js): a code's price and its systems' chips. */
962
- .fx-price__hint { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
963
- .fx-price__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; }
964
- .fx-price__head .fx-search { margin: 0; }
1028
+ /* Price list (price-list.js): one plain table of Flow's codes. */
1029
+ .fx-price { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
1030
+ .fx-price__head { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 8px 16px; }
1031
+ .fx-price__head .fx-search { margin: 0 0 6px; width: 260px; }
965
1032
  @media (max-width: 560px) { .fx-price__head .fx-search { width: 100%; } }
966
- .fx-price__systems { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 10px; }
967
- .fx-set__control .fx-price__link { width: 260px; min-width: 0; }
968
- @media (max-width: 560px) { .fx-set__control .fx-price__link { width: 150px; } }
1033
+ .fx-price__scroll { overflow-x: auto; border: 1px solid var(--fx-line); background: var(--t-paper); }
1034
+ .fx-price__table th { position: sticky; top: 0; z-index: 1; }
1035
+ .fx-price__table td { padding-top: 9px; padding-bottom: 9px; }
1036
+ .fx-price__table td.fx-num, .fx-price__table th.fx-num { text-align: right; white-space: nowrap; }
1037
+ .fx-price__code { width: 1%; white-space: nowrap; font-family: var(--t-data); font-size: 13px; font-weight: 600; color: var(--t-strong); }
1038
+ .fx-price__settings { margin-top: 12px; }
1039
+ .fx-ph__actions .fx-tabs__count[data-tone="warn"], .fx-btn .fx-tabs__count[data-tone="warn"] { margin-left: 8px; background: var(--t-warn-bg); color: var(--t-warn); }
1040
+ /* Price list › Reconcile (price-reconcile.js): Xero › Import's two views. */
1041
+ .fx-prc .fx-pager { margin-bottom: 4px; }
1042
+ .fx-prc__finder { display: flex; flex-direction: column; gap: 4px; }
1043
+ .fx-prc__found { display: flex; flex-direction: column; }
1044
+ .fx-prc__found:empty { display: none; }
1045
+ .fx-prc__found .fx-pop__item { display: grid; grid-template-columns: 90px 1fr auto; gap: 12px; text-align: left; }
1046
+ .fx-prc { padding-bottom: 72px; }
1047
+ .fx-prc-list td { white-space: nowrap; }
1048
+ .fx-prc-list td.fx-prc-list__name { white-space: normal; min-width: 160px; }
1049
+ .fx-prc-list__flow { display: inline-flex; align-items: center; gap: 8px; }
1050
+ .fx-prc-list__sys { width: 1%; white-space: nowrap; }
1051
+ .fx-prc-list__bad { color: var(--t-warn); font-weight: 600; }
1052
+ .fx-prc-list tr[data-state="sure"] .fx-xi-list__mid { color: var(--t-success); }
1053
+ .fx-prc-list tr[data-state="differs"] .fx-xi-list__mid { color: var(--t-warn); }
969
1054
  /* Custom fields (custom-fields.js): option chips with a colour dot, and the
970
1055
  dialog's option lines. The option colours are fixed, like ClickUp's. */
971
- .fx-cf__opt { display: inline-flex; align-items: center; gap: 6px; padding: 2px 9px; border: 1px solid var(--fx-line); border-radius: 999px; font-size: 12.5px; color: var(--t-text); white-space: nowrap; }
1056
+ .fx-cf__opt { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; padding: 2px 9px; border: 1px solid color-mix(in srgb, var(--cf) 45%, transparent); border-radius: 999px; background: color-mix(in srgb, var(--cf) 16%, transparent); font-size: 12.5px; font-weight: 600; color: var(--t-strong); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
972
1057
  .fx-cf__opt::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--cf); flex: none; }
973
1058
  [data-color="gray"] { --cf: #8a8f98; } [data-color="blue"] { --cf: #3b82f6; } [data-color="teal"] { --cf: #14b8a6; }
974
1059
  [data-color="green"] { --cf: #22a35a; } [data-color="amber"] { --cf: #e0a019; } [data-color="red"] { --cf: #e5484d; }
975
1060
  [data-color="purple"] { --cf: #8b5cf6; } [data-color="pink"] { --cf: #e5469a; }
976
- .fx-cf__options { display: grid; gap: 8px; }
977
- .fx-cf__line { display: grid; grid-template-columns: minmax(0, 1fr) 120px auto auto; gap: 8px; align-items: center; }
978
- .fx-cf__line .fx-input, .fx-cf__line .fx-select { width: 100%; min-width: 0; }
979
- .fx-cf__color { border-left: 4px solid var(--cf); }
980
- @media (max-width: 560px) { .fx-cf__line { grid-template-columns: minmax(0, 1fr) 96px; } }
1061
+ /* A colour picker (colorPicker): the button shows the real pill in the
1062
+ chosen colour; the palette shows the same pill in every colour. */
1063
+ .fx-cf__swatch { display: inline-flex; align-items: center; justify-content: space-between; gap: 6px; min-width: 0; max-width: 100%; min-height: 36px; padding: 4px 8px; border: 1px solid var(--fx-line); border-radius: var(--t-radius); background: var(--t-paper); color: var(--t-dim); font: inherit; cursor: pointer; }
1064
+ .fx-cf__swatch:hover:not(:disabled) { border-color: var(--t-dim); }
1065
+ .fx-cf__swatch:focus-visible { outline: none; box-shadow: var(--fx-focus); }
1066
+ .fx-cf__swatch:disabled { cursor: default; }
1067
+ .fx-cf__swatch > .fx-lucide { width: 14px; height: 14px; }
1068
+ .fx-cf__swatch > :first-child { min-width: 0; }
1069
+ .fx-cfm__group .fx-cf__swatch, .fx-cfm__type .fx-cf__swatch { min-height: 0; padding: 2px; border-color: transparent; background: none; }
1070
+ .fx-cfm__group .fx-cf__swatch > .fx-lucide, .fx-cfm__type .fx-cf__swatch > .fx-lucide { display: none; }
1071
+ .fx-cfm__group .fx-cf__swatch:hover, .fx-cfm__type .fx-cf__swatch:hover { border-color: var(--fx-line); background: var(--fx-hover); }
1072
+ .fx-pop.fx-cf__palette { min-width: 0; width: 248px; padding: 4px; }
1073
+ .fx-cf__grid { display: grid; gap: 2px; }
1074
+ .fx-cf__choice { display: grid; grid-template-columns: minmax(0, 1fr) auto 16px; align-items: center; gap: 8px; width: 100%; padding: 5px 8px; border: 0; border-radius: var(--t-radius); background: none; color: var(--t-dim); font: 500 12.5px/1.3 var(--t-body); text-align: left; cursor: pointer; }
1075
+ .fx-cf__choice:hover, .fx-cf__choice:focus-visible { background: var(--fx-hover); outline: none; }
1076
+ .fx-cf__choice[aria-checked="true"] { background: var(--fx-hover); color: var(--t-strong); }
1077
+ .fx-cf__choice > :first-child { justify-self: start; }
1078
+ .fx-cf__choice .fx-lucide { width: 15px; height: 15px; color: var(--t-accent); }
981
1079
  /* Custom fields manager (custom-fields.js), like ClickUp's Custom Field
982
1080
  Manager: a toolbar, then one table of type groups that fold. */
983
1081
  .fx-lucide { display: inline-block; flex: none; width: 16px; height: 16px; background: currentColor; -webkit-mask: var(--icon) center / contain no-repeat; mask: var(--icon) center / contain no-repeat; }
984
- .fx-cfm__card { padding: 0; overflow: hidden; }
1082
+ .fx-cfm__card { padding: 0; overflow: clip; }
985
1083
  .fx-cfm__bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 12px 16px; border-bottom: 1px solid var(--fx-line-soft); }
986
1084
  .fx-cfm__search { width: 220px; min-width: 0; height: 32px; }
987
1085
  .fx-cfm__tool { min-height: 32px; }
988
1086
  .fx-cfm__tool--dashed { border-style: dashed; }
989
1087
  .fx-cfm__gap { flex: 1; }
990
- .fx-cfm { display: grid; grid-template-columns: minmax(160px, 1fr) 64px 96px 120px minmax(90px, .6fr) 44px; }
1088
+ .fx-cfm { display: grid; grid-template-columns: minmax(200px, 1fr) 56px 88px 104px minmax(96px, 160px) 36px; column-gap: 16px; }
991
1089
  .fx-cfm > [role="row"], .fx-cfm > .fx-notice, .fx-cfm > .fx-cfm__create, .fx-cfm > .fx-cfm__empty { grid-column: 1 / -1; }
992
1090
  .fx-cfm__head, .fx-cfm__row { display: grid; grid-template-columns: subgrid; align-items: center; }
993
- .fx-cfm__head { height: 36px; padding: 0 8px 0 36px; color: var(--t-dim); font-size: 12.5px; border-bottom: 1px solid var(--fx-line-soft); }
1091
+ .fx-cfm__head { height: 36px; padding: 0 8px 0 40px; color: var(--t-dim); font-size: 12.5px; border-bottom: 1px solid var(--fx-line-soft); white-space: nowrap; }
994
1092
  .fx-cfm__sort { all: unset; cursor: pointer; display: inline-flex; gap: 4px; align-items: center; }
995
1093
  .fx-cfm__sort:focus-visible { box-shadow: var(--fx-focus); border-radius: 4px; }
996
1094
  .fx-cfm__sort .fx-lucide { width: 13px; height: 13px; }
997
1095
  .fx-cfm__group { display: flex; align-items: center; gap: 8px; height: 44px; margin-top: 12px; padding: 0 16px 0 8px; border-bottom: 1px solid var(--fx-line-soft); }
1096
+ .fx-cfm__group .fx-cfm__fold { margin-right: 0; }
998
1097
  .fx-cfm__group:first-of-type { margin-top: 4px; }
999
1098
  .fx-cfm__fold { all: unset; cursor: pointer; display: inline-grid; place-items: center; width: 24px; height: 24px; border-radius: 6px; color: var(--t-dim); }
1000
1099
  .fx-cfm__fold:hover { background: var(--fx-hover); color: var(--t-strong); }
@@ -1004,19 +1103,21 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
1004
1103
  .fx-cfm__pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 9px; border-radius: 6px; font-size: 12.5px; font-weight: 600; color: var(--cf); background: color-mix(in srgb, var(--cf) 14%, transparent); }
1005
1104
  .fx-cfm__pill .fx-lucide { width: 14px; height: 14px; }
1006
1105
  .fx-cfm__count { color: var(--t-dim); font-size: 12.5px; font-weight: 600; }
1007
- .fx-cfm__row { min-height: 36px; padding: 0 8px 0 36px; border-bottom: 1px solid var(--fx-line-soft); font-size: 14px; }
1008
- .fx-cfm__row:hover { background: var(--fx-hover); }
1106
+ .fx-cfm__row { min-height: 40px; padding: 4px 8px 4px 40px; border-bottom: 1px solid var(--fx-line-soft); font-size: 14px; }
1107
+ .fx-cfm__row:hover, .fx-cfm__row[data-open] { background: var(--fx-hover); }
1108
+ .fx-cfm__row[data-open] { box-shadow: inset 2px 0 0 var(--t-accent); }
1009
1109
  .fx-cfm__row[data-archived] .fx-cfm__label { color: var(--t-dim); }
1010
- .fx-cfm__name { display: flex; align-items: center; gap: 8px; min-width: 0; padding-right: 8px; color: var(--t-strong); }
1011
- .fx-cfm__name > .fx-lucide { width: 13px; height: 13px; color: var(--t-dim); }
1110
+ .fx-cfm__name { min-width: 0; color: var(--t-strong); }
1111
+ .fx-cfm__title { display: flex; align-items: center; gap: 8px; min-width: 0; min-height: 28px; }
1112
+ .fx-cfm__title > .fx-lucide { width: 13px; height: 13px; color: var(--t-dim); }
1012
1113
  .fx-cfm__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1013
1114
  .fx-cfm__edit { display: none; align-items: center; gap: 5px; flex: none; height: 24px; padding: 0 7px; border: 1px solid var(--fx-line); border-radius: 6px; background: var(--t-paper); color: var(--t-text); font: inherit; font-size: 12px; cursor: pointer; }
1014
1115
  .fx-cfm__edit .fx-lucide { width: 13px; height: 13px; }
1015
1116
  .fx-cfm__row:hover .fx-cfm__edit, .fx-cfm__edit:focus-visible { display: inline-flex; }
1016
1117
  .fx-cfm__edit:focus-visible { box-shadow: var(--fx-focus); outline: none; }
1017
1118
  .fx-cfm__rename { height: 28px; width: 100%; max-width: 320px; }
1018
- .fx-cfm__type { color: var(--cf); }
1019
- .fx-cfm__type .fx-lucide { width: 17px; height: 17px; }
1119
+ .fx-cfm__icon { display: inline-grid; place-items: center; width: 28px; height: 28px; border-radius: 6px; color: var(--cf); background: color-mix(in srgb, var(--cf) 14%, transparent); }
1120
+ .fx-cfm__icon .fx-lucide { width: 16px; height: 16px; }
1020
1121
  .fx-cfm__avatar { display: inline-grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; background: var(--t-primary); color: var(--t-on-primary); font-size: 10.5px; font-weight: 700; letter-spacing: .02em; }
1021
1122
  .fx-cfm__avatar .fx-lucide { width: 13px; height: 13px; }
1022
1123
  .fx-cfm__date, .fx-cfm__where { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -1025,7 +1126,7 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
1025
1126
  .fx-cfm__more { all: unset; cursor: pointer; display: inline-grid; place-items: center; width: 30px; height: 28px; border-radius: 6px; color: var(--t-dim); }
1026
1127
  .fx-cfm__more:hover, .fx-cfm__more[aria-expanded="true"] { background: var(--fx-hover); color: var(--t-strong); }
1027
1128
  .fx-cfm__more:focus-visible { box-shadow: var(--fx-focus); }
1028
- .fx-cfm__create { all: unset; cursor: pointer; display: flex; align-items: center; gap: 6px; height: 36px; padding: 0 16px 0 36px; color: var(--t-dim); font-size: 13px; }
1129
+ .fx-cfm__create { all: unset; cursor: pointer; display: flex; align-items: center; gap: 6px; height: 36px; padding: 0 16px 0 40px; color: var(--t-dim); font-size: 13px; }
1029
1130
  .fx-cfm__create:hover, .fx-cfm__create:focus-visible { color: var(--t-strong); background: var(--fx-hover); }
1030
1131
  .fx-cfm__create .fx-lucide { width: 14px; height: 14px; }
1031
1132
  .fx-cfm__empty { margin: 0; padding: 20px 16px; color: var(--t-dim); }
@@ -1034,8 +1135,63 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
1034
1135
  .fx-pop__item:disabled { opacity: .5; cursor: default; }
1035
1136
  .fx-pop__item[data-tone="danger"], .fx-pop__item[data-tone="danger"] .fx-pop__tick { color: var(--t-danger); }
1036
1137
  .fx-pop__item .fx-lucide { width: 15px; height: 15px; }
1138
+ /* The sidecar (custom-fields.js edit), like ClickUp's: a panel that slides
1139
+ in from the right of the manager and leaves the table usable. */
1140
+ .fx-cfm__card { position: relative; }
1141
+ .fx-cfm__card:has(> .fx-cfs:not([hidden])) { min-height: min(640px, 100vh); }
1142
+ .fx-cfs { position: absolute; inset: 0 0 0 auto; z-index: 5; display: flex; width: min(420px, 100%); border-left: 1px solid var(--fx-line); background: var(--t-paper); color: var(--t-text); box-shadow: -16px 0 40px rgba(0, 0, 0, .22); animation: fx-cfs-in .18s var(--fx-ease); }
1143
+ .fx-cfs[hidden] { display: none; }
1144
+ @keyframes fx-cfs-in { from { transform: translateX(24px); opacity: 0; } }
1145
+ @media (prefers-reduced-motion: reduce) { .fx-cfs { animation: none; } }
1146
+ .fx-cfs__form { position: sticky; top: 0; display: flex; flex-direction: column; width: 100%; height: min(100%, 100vh); max-height: 100vh; min-height: 0; }
1147
+ .fx-cfs__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 52px; padding: 0 12px 0 16px; border-bottom: 1px solid var(--fx-line-soft); }
1148
+ .fx-cfs__typename, .fx-cfs__typepick { display: inline-flex; align-items: center; gap: 8px; color: var(--t-text); font-weight: 500; font-size: 14px; }
1149
+ .fx-cfs__typepick { position: relative; padding: 4px 8px; margin-left: -8px; border-radius: 6px; cursor: pointer; }
1150
+ .fx-cfs__typepick:hover, .fx-cfs__typepick:focus-within { background: var(--fx-hover); }
1151
+ .fx-cfs__typepick .fx-lucide { width: 15px; height: 15px; }
1152
+ .fx-cfs__type { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
1153
+ .fx-cfs__close, .fx-cfs__delete, .fx-cfs__tool { all: unset; cursor: pointer; display: inline-grid; place-items: center; width: 30px; height: 30px; border-radius: 6px; color: var(--t-dim); }
1154
+ .fx-cfs__close:hover, .fx-cfs__tool:hover:not(:disabled) { background: var(--fx-hover); color: var(--t-strong); }
1155
+ .fx-cfs__close:focus-visible, .fx-cfs__delete:focus-visible, .fx-cfs__tool:focus-visible { box-shadow: var(--fx-focus); }
1156
+ .fx-cfs__body { flex: 1; min-height: 0; overflow-y: auto; display: grid; align-content: start; gap: 0; }
1157
+ .fx-cfs__section { display: grid; gap: 8px; padding: 14px 16px 4px; }
1158
+ .fx-cfs__section:last-child { margin-top: 14px; padding-top: 16px; border-top: 1px solid var(--fx-line-soft); }
1159
+ .fx-cfs__section[hidden] { display: none; }
1160
+ .fx-cfs__label { color: var(--t-text); font-size: 12.5px; font-weight: 500; }
1161
+ .fx-cfs__req { margin-left: 2px; color: var(--t-danger); }
1162
+ .fx-cfs__hint { font-size: 12.5px; }
1163
+ .fx-cfs__namebox, .fx-cfs__opt { display: flex; align-items: center; gap: 8px; min-height: 32px; padding: 0 8px; border: 1px solid var(--fx-line); border-radius: var(--t-radius); background: var(--t-paper); }
1164
+ .fx-cfs__namebox:focus-within, .fx-cfs__opt:focus-within { border-color: var(--t-accent); box-shadow: var(--fx-focus); }
1165
+ .fx-cfs__nameicon { display: inline-grid; place-items: center; color: var(--cf); }
1166
+ .fx-cfs__nameicon .fx-lucide { width: 16px; height: 16px; }
1167
+ .fx-cfs__name, .fx-cfs__optname { flex: 1; min-width: 0; height: 30px; padding: 0; border: 0; border-radius: 0; background: none; color: var(--t-text); font: 400 14px/30px var(--t-body); outline: none; box-shadow: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1168
+ .fx-cfs__name:focus, .fx-cfs__optname:focus { outline: none; box-shadow: none; border: 0; }
1169
+ .fx-cfs__list, .fx-cfs__options { display: grid; gap: 6px; }
1170
+ .fx-cfs__opt--locked { padding-left: 12px; }
1171
+ .fx-cfs__dot[data-rank] { --cf: var(--t-accent); }
1172
+ .fx-cfs__dot[data-rank="0"] { --cf: var(--t-mute); }
1173
+ .fx-cfs__dot[data-rank="3"], .fx-cfs__dot[data-rank="4"] { --cf: color-mix(in srgb, var(--t-accent) 45%, var(--t-success)); }
1174
+ .fx-cfs__dot[data-rank="5"], .fx-cfs__dot[data-rank="6"] { --cf: var(--t-success); }
1175
+ .fx-cfs__dot[data-rank="7"] { --cf: var(--t-dim); }
1176
+ .fx-cfs__colour.fx-cf__swatch { min-height: 0; padding: 4px; border: 0; background: none; border-radius: 50%; }
1177
+ .fx-cfs__colour.fx-cf__swatch:hover:not(:disabled) { background: var(--fx-hover); }
1178
+ .fx-cfs__dot { display: block; width: 12px; height: 12px; border-radius: 50%; background: var(--cf); }
1179
+ .fx-cfs__tools { display: inline-flex; opacity: 0; transition: opacity .12s var(--fx-ease); }
1180
+ .fx-cfs__opt:hover .fx-cfs__tools, .fx-cfs__opt:focus-within .fx-cfs__tools { opacity: 1; }
1181
+ .fx-cfs__tool { width: 26px; height: 26px; }
1182
+ .fx-cfs__tool:disabled { opacity: .35; cursor: default; }
1183
+ .fx-cfs__tool .fx-lucide { width: 14px; height: 14px; }
1184
+ .fx-cfs__add { all: unset; cursor: pointer; display: flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 10px; border: 1px dashed var(--fx-line); border-radius: var(--t-radius); color: var(--t-dim); font-size: 13.5px; }
1185
+ .fx-cfs__add:hover, .fx-cfs__add:focus-visible { color: var(--t-strong); border-color: var(--t-dim); }
1186
+ .fx-cfs__add .fx-lucide { width: 14px; height: 14px; }
1187
+ .fx-cfs__where { display: flex; align-items: center; gap: 8px; color: var(--t-text); font-size: 14px; }
1188
+ .fx-cfs__where .fx-lucide { width: 16px; height: 16px; color: var(--t-dim); }
1189
+ .fx-cfs__foot { display: flex; align-items: center; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--fx-line-soft); }
1190
+ .fx-cfs__delete { color: var(--t-danger); background: color-mix(in srgb, var(--t-danger) 12%, transparent); }
1191
+ .fx-cfs__delete:hover { background: color-mix(in srgb, var(--t-danger) 22%, transparent); }
1192
+ .fx-cfs__delete .fx-lucide { width: 16px; height: 16px; }
1037
1193
  @media (max-width: 720px) {
1038
- .fx-cfm { grid-template-columns: minmax(0, 1fr) 40px 40px; }
1194
+ .fx-cfm { grid-template-columns: minmax(0, 1fr) 36px 32px; column-gap: 8px; }
1039
1195
  .fx-cfm .fx-cfm__by, .fx-cfm .fx-cfm__date, .fx-cfm .fx-cfm__where { display: none; }
1040
1196
  .fx-cfm__head, .fx-cfm__row { padding-left: 16px; }
1041
1197
  .fx-cfm__create { padding-left: 16px; }
@@ -20,7 +20,7 @@ export function sendModeNote(to, sendMode, what = 'each invoice') {
20
20
 
21
21
  // The line under a draft's title, one fact per sentence.
22
22
  export function draftNote(to, invoice, ready) {
23
- if (!to) return ready ? 'A person checked it. A saved change keeps it ready.' : 'Flow gives it a number when you issue it.';
24
- if (!ready) return `Finalise it, then send it to ${to}.`;
23
+ if (!to) return ready ? 'It has no problem. Approve gives it a number.' : 'Fix the problem, then approve it.';
24
+ if (!ready) return `Fix the problem, then send it to ${to}.`;
25
25
  return sendModeNote(to, invoice?.sendMode, 'this invoice');
26
26
  }