@arcforgelabs/flow 2026.10.46 → 2026.10.47

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.
@@ -282,6 +282,14 @@ a.fx-link:hover, a.fx-link:focus-visible { border-color: var(--t-strong); }
282
282
  box-shadow: 0 8px 24px rgba(0, 0, 0, .18); animation: fx-rise 160ms var(--fx-ease);
283
283
  }
284
284
  .fx-toast[data-tone="danger"] { background: var(--t-danger); color: var(--t-paper); }
285
+ .fx-toast--stay { display: flex; align-items: flex-start; gap: 12px; }
286
+ .fx-toast__text { flex: 1; min-width: 0; }
287
+ .fx-toast__close {
288
+ flex: none; display: grid; place-items: center; width: 28px; height: 28px; margin: -6px -8px -6px 0;
289
+ border: 0; border-radius: var(--t-radius); background: transparent; color: inherit; cursor: pointer; opacity: .85;
290
+ }
291
+ .fx-toast__close:hover { opacity: 1; background: color-mix(in srgb, currentColor 14%, transparent); }
292
+ .fx-toast__close svg { width: 16px; height: 16px; }
285
293
 
286
294
  /* ── Table and people ───────────────────────────────────────────────────── */
287
295
  .fx-table { width: 100%; border-collapse: collapse; font-size: 14px; }
@@ -585,6 +593,30 @@ a.fx-strong:hover { text-decoration: underline; }
585
593
  .fx-pop__item[aria-checked="true"] { color: var(--t-strong); font-weight: 600; }
586
594
  .fx-pop__tick { display: inline-grid; place-items: center; flex: none; width: 16px; height: 16px; color: var(--t-accent); }
587
595
  .fx-pop__sep { height: 1px; margin: 6px 4px; background: var(--fx-line-soft); }
596
+ /* A row with a trailing control (the sidebar pin). The control does not
597
+ close the menu. An unpinned pin shows when the pointer is on the row. */
598
+ .fx-pop__row { display: flex; align-items: center; gap: 2px; }
599
+ .fx-pop__row > .fx-pop__item { flex: 1; min-width: 0; }
600
+ .fx-pop__row > .fx-pop__item > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
601
+ .fx-pop__pin, .fx-pop__pin--fixed {
602
+ display: inline-grid; place-items: center; flex: none; width: 28px; height: 28px;
603
+ border: 0; border-radius: var(--t-radius); background: none; color: var(--t-dim);
604
+ }
605
+ .fx-pop__pin { cursor: pointer; }
606
+ .fx-pop__pin .fx-lucide, .fx-pop__pin--fixed .fx-lucide, .fx-pop__pin .fx-ico, .fx-pop__pin--fixed .fx-ico { width: 15px; height: 15px; }
607
+ .fx-pop__pin[aria-pressed="false"] { opacity: 0; }
608
+ .fx-pop__row:hover .fx-pop__pin[aria-pressed="false"],
609
+ .fx-pop__row:focus-within .fx-pop__pin[aria-pressed="false"],
610
+ .fx-pop__pin:focus-visible { opacity: 1; }
611
+ .fx-pop__pin[aria-pressed="true"], .fx-pop__pin--fixed { color: var(--t-strong); opacity: 1; }
612
+ .fx-pop__pin:hover, .fx-pop__pin:focus-visible { background: var(--fx-hover); color: var(--t-strong); outline: none; }
613
+ .fx-pop.fx-pop--side { min-width: 240px; }
614
+ .fx-pop--side .fx-pop__tick { color: inherit; }
615
+ @media (hover: none) { .fx-pop__pin[aria-pressed="false"] { opacity: 1; } }
616
+ @media (max-width: 820px) {
617
+ .fx-pop--side .fx-pop__item { min-height: 44px; }
618
+ .fx-pop--side .fx-pop__pin, .fx-pop--side .fx-pop__pin--fixed { width: 44px; height: 44px; }
619
+ }
588
620
  .fx-pop__foot { display: flex; justify-content: flex-end; padding: 6px 4px 2px; border-top: 1px solid var(--fx-line-soft); margin-top: 6px; }
589
621
  /* A chip with an × that clears it (as ClickUp's "Clear all filters"). */
590
622
  .fx-chipset { display: inline-flex; align-items: stretch; }
@@ -798,6 +830,29 @@ th:hover > .fx-th-grip::after { background: var(--t-brd-hi); }
798
830
  .fx-main--board .fx-board { height: 100%; overflow-y: hidden; align-items: stretch; }
799
831
  .fx-main--board .fx-board__cards { flex: 1; max-height: none; }
800
832
  }
833
+ /* Phone: one lane fills the width and snaps. Stage tabs (jobs.js) name the
834
+ lane; the lane's own head is hidden. Drag stays on a wider screen. */
835
+ .fx-board-tabs { display: none; }
836
+ @media (max-width: 639px) {
837
+ .fx-main--board .fx-board-tabs:not([hidden]) {
838
+ display: flex; flex: 1 1 100%; order: -1; gap: 2px; min-width: 0; max-width: 100%;
839
+ overflow-x: auto; scrollbar-width: none;
840
+ }
841
+ .fx-board-tabs button {
842
+ flex: none; display: inline-flex; align-items: center; gap: 8px; min-width: 44px; min-height: 44px;
843
+ padding: 0 12px; border: 0; border-bottom: 2px solid transparent; background: transparent;
844
+ color: var(--t-dim); font: 600 14px var(--t-body); cursor: pointer;
845
+ }
846
+ .fx-board-tabs button[aria-selected="true"] { color: var(--t-strong); border-bottom-color: var(--t-brand); }
847
+ .fx-main--board .fx-frame__body { padding: 0; }
848
+ .fx-main--board .fx-board {
849
+ --fx-lane: 100%; grid-auto-columns: 100%; gap: 0; min-width: 0; width: 100%;
850
+ scroll-snap-type: x mandatory; scrollbar-width: none;
851
+ }
852
+ .fx-main--board .fx-board__col { scroll-snap-align: start; scroll-snap-stop: always; border-radius: 0; }
853
+ .fx-main--board .fx-board__head { display: none; }
854
+ .fx-main--board .fx-board__cards { max-height: none; padding: 8px; }
855
+ }
801
856
  .fx-board__none { padding: 8px 4px; color: var(--t-dim); font-size: 13px; }
802
857
  .fx-table--jobs tbody tr { cursor: pointer; }
803
858
 
@@ -972,13 +1027,74 @@ th:hover > .fx-th-grip::after { background: var(--t-brd-hi); }
972
1027
  .fx-task__activity li::after { content: ""; position: absolute; left: 5.5px; top: 16px; bottom: 0; width: 1px; background: var(--fx-line); }
973
1028
  .fx-task__activity li:last-child::after { display: none; }
974
1029
  .fx-task__activity time { display: block; margin-top: 1px; color: var(--t-mute); font-size: 11.5px; }
975
- @media (max-width: 760px) {
976
- .fx-dialog.fx-task { width: 100vw; max-width: 100vw; height: 100vh; max-height: 100vh; margin: 0; border-radius: 0; }
977
- .fx-task__grid { grid-template-columns: minmax(0, 1fr); }
978
- .fx-task__main { padding: 16px; }
979
- .fx-task__fields { grid-template-columns: minmax(0, 1fr); }
980
- .fx-task__grid:has(> .fx-sidecar [data-tab="fix"][aria-selected="true"]) { grid-template-columns: minmax(0, 1fr); }
981
- .fx-task__side { border-top: 1px solid var(--fx-line); border-left: 0; }
1030
+ /* A record's phone bar and foot. Hidden above a phone, so the desk panel and
1031
+ the invoice header stay as they are. */
1032
+ .fx-record, .fx-record__foot { display: none; }
1033
+ @media (max-width: 639px) {
1034
+ /* The job panel is the whole screen. Back, the client and the next job
1035
+ sit in the bar; Activity and Fix are tabs under it; the action is fixed
1036
+ at the foot, in thumb reach. */
1037
+ .fx-dialog.fx-task {
1038
+ display: flex; flex-direction: column; inset: 0; width: 100%; max-width: none;
1039
+ height: 100%; max-height: none; margin: 0; border-radius: 0; overflow: hidden;
1040
+ }
1041
+ .fx-dialog.fx-task > .fx-record { flex: none; }
1042
+ .fx-dialog.fx-task .fx-task__grid {
1043
+ display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: auto;
1044
+ grid-template-columns: minmax(0, 1fr);
1045
+ }
1046
+ .fx-dialog.fx-task .fx-task__main { padding: 16px; }
1047
+ .fx-dialog.fx-task .fx-task__fields { grid-template-columns: minmax(0, 1fr); }
1048
+ .fx-dialog.fx-task .fx-task__head, .fx-dialog.fx-task .fx-task__title { display: none; }
1049
+ .fx-dialog.fx-task .fx-task__grid:has(> .fx-sidecar [data-tab="fix"][aria-selected="true"]) { grid-template-columns: minmax(0, 1fr); }
1050
+ .fx-task__grid > .fx-sidecar__head {
1051
+ position: sticky; top: 0; z-index: 2; flex: none;
1052
+ background: var(--t-paper); border-bottom: 1px solid var(--fx-line-soft);
1053
+ }
1054
+ .fx-task__grid > .fx-sidecar__head .fx-sidecar__close, .fx-sidecar--page .fx-sidecar__close { display: none; }
1055
+ .fx-task__grid > .fx-sidecar__head .fx-sidecar__tab, .fx-sidecar--page .fx-sidecar__tab { min-width: 44px; min-height: 44px; }
1056
+ .fx-sidecar--page { border-left: 0; border-top: 1px solid var(--fx-line); }
1057
+ .fx-sidecar--page .fx-sidecar__body { scroll-margin-top: 52px; }
1058
+ .fx-dialog.fx-task .fx-task__actions:not(:empty) {
1059
+ position: fixed; left: 0; right: 0; bottom: 0; z-index: 5; margin: 0;
1060
+ padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
1061
+ background: var(--t-paper); border-top: 1px solid var(--fx-line);
1062
+ }
1063
+ .fx-dialog.fx-task .fx-task__actions .fx-btn { flex: 1; min-height: 44px; }
1064
+ .fx-dialog.fx-task:has(.fx-task__actions:not(:empty)) .fx-task__grid,
1065
+ .fx-dialog.fx-task:has([data-action="accept"]) .fx-task__grid { padding-bottom: 68px; }
1066
+ .fx-dialog.fx-task:has([data-action="accept"]) .fx-task__actions { display: none; }
1067
+ .fx-invoice [data-action="accept"], .fx-dialog.fx-task [data-action="accept"] {
1068
+ position: fixed; left: 8px; right: 8px; bottom: 8px; z-index: 6; width: auto; min-height: 44px;
1069
+ }
1070
+ .fx-record {
1071
+ display: flex; align-items: center; gap: 2px; min-height: 48px; padding: 2px 4px;
1072
+ background: var(--t-paper); border-bottom: 1px solid var(--fx-line-soft);
1073
+ }
1074
+ .fx-record__back, .fx-record__step, .fx-record__menu {
1075
+ flex: none; display: inline-grid; place-items: center; width: 44px; height: 44px;
1076
+ min-width: 44px; min-height: 44px; padding: 0; border: 0; border-radius: var(--t-radius);
1077
+ background: transparent; color: var(--t-strong); cursor: pointer;
1078
+ }
1079
+ .fx-record__back:disabled, .fx-record__step:disabled { opacity: 0.35; cursor: default; }
1080
+ .fx-record__back:focus-visible, .fx-record__step:focus-visible, .fx-record__menu:focus-visible { box-shadow: var(--fx-focus); }
1081
+ .fx-record__menu[hidden] { display: none; }
1082
+ .fx-record__title, .fx-record .fx-title {
1083
+ flex: 1; min-width: 0; margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1084
+ color: var(--t-strong); font: 600 16px/1.2 var(--t-body); letter-spacing: 0;
1085
+ }
1086
+ .fx-record .fx-ph { flex: 1; min-width: 0; min-height: 0; gap: 8px; background: transparent; }
1087
+ .fx-record .fx-ph__text { min-width: 0; }
1088
+ .fx-record__end { display: flex; flex: none; align-items: center; }
1089
+ .fx-record__foot:not([hidden]) {
1090
+ display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 5; gap: 8px;
1091
+ padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
1092
+ background: var(--t-paper); border-top: 1px solid var(--fx-line);
1093
+ }
1094
+ .fx-record__foot .fx-btn { flex: 1; min-height: 44px; }
1095
+ .fx-invoice > .fx-pager, .fx-invoice .fx-ph__actions { display: none; }
1096
+ .fx-invoice:has(.fx-record__foot:not([hidden])), .fx-invoice:has([data-action="accept"]) { padding-bottom: 72px; }
1097
+ .fx-invoice:has([data-action="accept"]) .fx-record__foot { display: none; }
982
1098
  }
983
1099
 
984
1100
  /* ── Dialog ─────────────────────────────────────────────────────────────── */
@@ -992,6 +1108,17 @@ th:hover > .fx-th-grip::after { background: var(--t-brd-hi); }
992
1108
  .fx-dialog__form { display: grid; gap: 16px; padding: 22px; }
993
1109
  .fx-dialog__title { color: var(--t-strong); font: var(--t-display-weight) 21px/1.2 var(--t-display); }
994
1110
  .fx-dialog__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
1111
+ .fx-dialog[data-phone-layout="sheet"] {
1112
+ position: fixed; inset: auto 0 0 0; width: 100%; max-width: 100%; max-height: min(80vh, 100%);
1113
+ margin: 0; border-radius: var(--t-radius-lg) var(--t-radius-lg) 0 0;
1114
+ }
1115
+ .fx-sheet__choices { display: grid; gap: 4px; }
1116
+ .fx-sheet__choice {
1117
+ display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%;
1118
+ min-height: 44px; padding: 0 12px; border: 0; border-radius: var(--t-radius);
1119
+ background: transparent; color: var(--t-strong); font: 600 15px var(--t-body); text-align: left; cursor: pointer;
1120
+ }
1121
+ .fx-sheet__choice:hover { background: var(--fx-hover); }
995
1122
 
996
1123
  /* Search (search.js): a box near the top, results grouped under it. */
997
1124
  .fx-search-dialog { width: min(600px, calc(100vw - 32px)); margin-top: 12vh; }
@@ -1152,6 +1279,7 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
1152
1279
  background: rgba(0, 0, 0, .32);
1153
1280
  }
1154
1281
  .fx-nav__report { margin-top: 8px; }
1282
+ .fx-nav a, .fx-nav__report, .fx-nav__more { min-height: 44px; }
1155
1283
  .fx-side__end { margin-left: auto; padding: 0; border-top: 0; }
1156
1284
  #user-button { flex: none; }
1157
1285
  }
@@ -1174,13 +1302,34 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
1174
1302
  /* ── Phones ─────────────────────────────────────────────────────────────── */
1175
1303
  @media (max-width: 639px) {
1176
1304
  .fx-main { padding-top: 20px; }
1305
+ /* A two-line list needs the rows, not the gaps, so seven fit at 375×667.
1306
+ The alert stays one line. The toolbar stays one scrolling row. */
1307
+ .fx-main--frame:has([data-phone-layout="two-line"]) { gap: 8px; padding-top: 12px; }
1308
+ .fx-alerts { flex-wrap: nowrap; }
1309
+ .fx-alerts__text { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1310
+ /* Tabs stay on their own row. The tools beside them stay one row and
1311
+ scroll, so a chip is never covered by the next control. */
1312
+ .fx-listhead, .fx-listhead > .fx-toolbar { min-width: 0; max-width: 100%; }
1313
+ .fx-listhead .fx-toolbar__tools { flex: 1 1 auto; min-width: 0; overflow-x: auto; scrollbar-width: none; }
1314
+ .fx-listhead .fx-toolbar__end,
1315
+ .fx-listhead .fx-toolbar__end:has(> .fx-toolbar__tools) {
1316
+ flex: 1 1 100%; flex-wrap: nowrap; min-width: 0; overflow-x: auto; scrollbar-width: none; row-gap: 0;
1317
+ }
1177
1318
  .fx-title { font-size: 22px; }
1178
1319
  .fx-table thead { display: none; }
1179
1320
  .fx-table.fx-table--sized { width: 100% !important; min-width: 0; table-layout: auto; }
1180
1321
  .fx-table--sized td { white-space: normal; }
1181
1322
  .fx-table--sized colgroup { display: none; }
1182
1323
  .fx-table--sized td.fx-c-end { display: none; }
1183
- .fx-table, .fx-table tbody, .fx-table tr, .fx-table td { display: block; width: 100%; }
1324
+ /* Tables outside a column list still stack. People keeps its own grid. */
1325
+ .fx-table-scroll:not([data-phone-layout="two-line"]) .fx-table,
1326
+ .fx-table-scroll:not([data-phone-layout="two-line"]) .fx-table tbody,
1327
+ .fx-table-scroll:not([data-phone-layout="two-line"]) .fx-table tr,
1328
+ .fx-table-scroll:not([data-phone-layout="two-line"]) .fx-table td,
1329
+ .fx-table:not(.fx-table--sized, .fx-people),
1330
+ .fx-table:not(.fx-table--sized, .fx-people) tbody,
1331
+ .fx-table:not(.fx-table--sized, .fx-people) tr,
1332
+ .fx-table:not(.fx-table--sized, .fx-people) td { display: block; width: 100%; }
1184
1333
  .fx-table tr { padding: 12px 14px; border-bottom: 1px solid var(--fx-line-soft); }
1185
1334
  .fx-table td { padding: 4px 0; border: 0; }
1186
1335
  .fx-table td:nth-child(2), .fx-table td:nth-child(3) { display: inline-flex; width: auto; margin: 6px 10px 0 0; vertical-align: middle; }
@@ -1197,20 +1346,58 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
1197
1346
  .fx-people td:nth-child(2) .fx-select { width: 100%; }
1198
1347
  .fx-people td:nth-child(3) { grid-area: 3 / 1 / 4 / -1; }
1199
1348
  .fx-people td:nth-child(3):empty { display: none; }
1200
- /* Jobs list: the tick at the left of each row and group head; the client
1201
- and total on the first line, then each other column on its own line,
1202
- with its name. */
1203
- .fx-table--jobs tr { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; align-items: center; gap: 4px 8px; }
1204
- .fx-table--jobs td, .fx-table--jobs td:nth-child(2), .fx-table--jobs td:nth-child(3) { display: block; width: auto; margin: 0; padding: 0; }
1205
- .fx-table--jobs tr > td.fx-tick-cell { grid-column: 1; grid-row: 1; align-self: start; padding-top: 2px; }
1206
- .fx-table--jobs tr.fx-row-link > td:not(.fx-tick-cell) { grid-column: 2 / -1; }
1207
- .fx-table--jobs tr.fx-row-link > td.fx-c-client { grid-area: 1 / 2; }
1208
- .fx-table--jobs tr.fx-row-link > td.fx-c-total { grid-area: 1 / 3; }
1209
- .fx-table--jobs td.fx-c-end { display: none; }
1210
- .fx-table--jobs tr.fx-row-link > td[data-label]:not(.fx-c-client, .fx-c-total, .fx-c-stage)::before { content: attr(data-label) " "; color: var(--t-dim); font-size: 12px; }
1211
- .fx-table--jobs tr.fx-table__group { grid-template-columns: 28px minmax(0, 1fr); background: var(--t-s1); }
1212
- .fx-table--jobs tr.fx-table__group > td.fx-tick-cell { grid-row: 1; align-self: center; padding: 0; }
1213
- .fx-table--jobs tr.fx-table__group > td { background: none; }
1349
+ /* Column lists (columns.js): two lines, 56px. Line 1 is the title columns
1350
+ and the amount. Line 2 is the status and up to three meta columns.
1351
+ The tick stays at the left with a 44px hit area. */
1352
+ [data-phone-layout="two-line"] .fx-table,
1353
+ [data-phone-layout="two-line"] .fx-table tbody { display: block; width: 100% !important; min-width: 0; }
1354
+ [data-phone-layout="two-line"] .fx-table tbody tr:not(.fx-table__group) {
1355
+ display: flex; flex-wrap: wrap; align-items: center; position: relative;
1356
+ box-sizing: border-box; height: 56px; min-height: 56px; max-height: 56px;
1357
+ margin: 0; padding: 6px 12px 6px 44px; overflow: hidden;
1358
+ border-bottom: 1px solid var(--fx-line-soft);
1359
+ }
1360
+ [data-phone-layout="two-line"] .fx-table tbody tr:not(.fx-table__group)::after {
1361
+ content: ""; order: 3; flex-basis: 100%; height: 0;
1362
+ }
1363
+ [data-phone-layout="two-line"] .fx-table td,
1364
+ [data-phone-layout="two-line"] .fx-table td:nth-child(2),
1365
+ [data-phone-layout="two-line"] .fx-table td:nth-child(3) {
1366
+ display: block; width: auto; margin: 0; padding: 0; border: 0;
1367
+ white-space: nowrap; background: none;
1368
+ }
1369
+ [data-phone-layout="two-line"] .fx-tick-cell {
1370
+ position: absolute; top: 0; left: 0; display: flex; align-items: center; justify-content: center;
1371
+ width: 44px; height: 56px; padding: 0;
1372
+ }
1373
+ [data-phone-layout="two-line"] .fx-tick-cell .fx-tick { width: 44px; height: 44px; margin: 0; }
1374
+ [data-phone-layout="two-line"] [data-phone-slot="title"] {
1375
+ order: 1; flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis;
1376
+ color: var(--t-strong); font-weight: 600;
1377
+ }
1378
+ [data-phone-layout="two-line"] [data-phone-slot="title"] :is(a, button) {
1379
+ display: inline-flex; align-items: center; max-width: 100%; min-height: 44px;
1380
+ }
1381
+ [data-phone-layout="two-line"] [data-phone-slot="end"] {
1382
+ order: 2; flex: none; margin-left: 8px; font-variant-numeric: tabular-nums;
1383
+ }
1384
+ [data-phone-layout="two-line"] [data-phone-slot="meta"] {
1385
+ order: 5; flex: 0 1 auto; min-width: 0; max-width: 42%; overflow: hidden; text-overflow: ellipsis;
1386
+ color: var(--t-dim); font-size: 12px; line-height: 16px;
1387
+ }
1388
+ [data-phone-layout="two-line"] td.fx-c-stage,
1389
+ [data-phone-layout="two-line"] td.fx-c-status { order: 4; color: inherit; }
1390
+ [data-phone-layout="two-line"] .fx-stage { padding-block: 0; line-height: 16px; }
1391
+ [data-phone-layout="two-line"] [data-phone-slot="hide"],
1392
+ [data-phone-layout="two-line"] td.fx-c-end { display: none; }
1393
+ [data-phone-layout="two-line"] tr.fx-table__group {
1394
+ display: flex; align-items: center; box-sizing: border-box; height: 32px; min-height: 32px; max-height: 32px;
1395
+ margin: 0; padding: 0 8px 0 44px; overflow: visible; background: var(--t-s1);
1396
+ border-bottom: 1px solid var(--fx-line-soft);
1397
+ }
1398
+ [data-phone-layout="two-line"] tr.fx-table__group td { height: 32px; padding: 0; background: none; }
1399
+ [data-phone-layout="two-line"] tr.fx-table__group .fx-tick-cell { height: 32px; }
1400
+ [data-phone-layout="two-line"] tr.fx-table__group .fx-tick-cell .fx-tick { width: 44px; height: 44px; }
1214
1401
  }
1215
1402
 
1216
1403
  @keyframes fx-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@@ -1239,7 +1426,10 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
1239
1426
  .fx-price { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
1240
1427
  .fx-price__head { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 8px 16px; }
1241
1428
  .fx-price__head .fx-search { margin: 0 0 6px; width: 260px; }
1242
- @media (max-width: 639px) { .fx-price__head .fx-search { width: 100%; } }
1429
+ @media (max-width: 639px) {
1430
+ .fx-price__head .fx-search { width: 100%; }
1431
+ .fx-price__settings .fx-select { min-height: 44px; }
1432
+ }
1243
1433
  .fx-price__table th { position: sticky; top: 0; z-index: 1; }
1244
1434
  .fx-price__table td { padding-top: 9px; padding-bottom: 9px; }
1245
1435
  .fx-price__table td.fx-num, .fx-price__table th.fx-num { text-align: right; white-space: nowrap; }
@@ -1411,13 +1601,27 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
1411
1601
  .fx-cfs__delete { color: var(--t-danger); background: color-mix(in srgb, var(--t-danger) 12%, transparent); }
1412
1602
  .fx-cfs__delete:hover { background: color-mix(in srgb, var(--t-danger) 28%, transparent); }
1413
1603
  .fx-cfs__delete .fx-lucide { width: 16px; height: 16px; }
1414
- @media (max-width: 720px) {
1604
+ @media (max-width: 639px) {
1415
1605
  .fx-cfm { grid-template-columns: minmax(0, 1fr) 36px 32px; column-gap: 8px; }
1416
1606
  .fx-cfm .fx-cfm__by, .fx-cfm .fx-cfm__date, .fx-cfm .fx-cfm__where { display: none; }
1417
1607
  .fx-cfm__head, .fx-cfm__row { padding-left: 16px; }
1418
1608
  .fx-cfm__create { padding-left: 16px; }
1419
1609
  .fx-cfm__search { flex: 1 1 100%; width: auto; }
1420
1610
  .fx-cfm__edit { display: none !important; }
1611
+ /* Custom fields uses the same phone slots as a column list: name and the
1612
+ menu on line 1, up to three facts on line 2, 56px. */
1613
+ .fx-cfm__row {
1614
+ display: flex; flex-wrap: wrap; align-items: center; box-sizing: border-box;
1615
+ height: 56px; min-height: 56px; max-height: 56px; padding: 6px 8px 6px 16px; overflow: hidden;
1616
+ }
1617
+ .fx-cfm__row::after { content: ""; order: 3; flex-basis: 100%; height: 0; }
1618
+ .fx-cfm__row > * { display: none; }
1619
+ .fx-cfm__row > [data-phone-slot="title"] { display: block; order: 1; flex: 1 1 0; min-width: 0; }
1620
+ .fx-cfm__row > [data-phone-slot="end"] { display: flex; order: 2; flex: none; align-items: center; }
1621
+ .fx-cfm__row > [data-phone-slot="meta"] {
1622
+ display: block; order: 5; flex: 0 1 auto; min-width: 0; max-width: 40%; overflow: hidden;
1623
+ text-overflow: ellipsis; white-space: nowrap; color: var(--t-dim); font-size: 12px; line-height: 16px;
1624
+ }
1421
1625
  }
1422
1626
  /* Invoice rules (invoice-rules.js): a row's Edit button and switch, and the
1423
1627
  rule dialog's condition lines. */
@@ -1556,6 +1760,8 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
1556
1760
  }
1557
1761
  .fx-bulk[hidden] { display: none; }
1558
1762
  .fx-bulk[aria-busy="true"] { opacity: .7; pointer-events: none; }
1763
+ .fx-bulk[aria-busy="true"]:has([data-busy]) { opacity: 1; }
1764
+ .fx-bulk__act .fx-spinner { flex: none; border-color: color-mix(in srgb, currentColor 35%, transparent); border-top-color: currentColor; }
1559
1765
  .fx-bulk__acts { display: flex; align-items: center; gap: 2px; min-width: 0; }
1560
1766
  .fx-bulk__count { display: inline-flex; align-items: center; gap: 6px; flex: none; margin-right: 6px; padding: 3px 4px 3px 10px; border: 1px solid var(--t-accent-brd, var(--fx-line)); border-radius: var(--t-radius); background: var(--t-accent-bg); color: var(--t-accent); font-weight: 600; }
1561
1767
  .fx-bulk__clear, .fx-bulk__act { position: relative; display: inline-flex; align-items: center; gap: 6px; padding: 7px 9px; border: 1px solid transparent; border-radius: var(--t-radius); background: none; color: inherit; font: inherit; cursor: pointer; }
@@ -1584,6 +1790,11 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
1584
1790
  .fx-bulk__sep { margin: 0 2px; }
1585
1791
  .fx-bulk__act:not(.fx-bulk__act--primary) .fx-bulk__word { display: none; }
1586
1792
  }
1793
+ /* Phone and tablet: Delete and Void are a full touch target. */
1794
+ @media (max-width: 1023px) {
1795
+ .fx-invoice .fx-ph__actions .fx-btn--danger { min-height: 44px; }
1796
+ .fx-bulk__act[data-tone="danger"] { min-width: 44px; min-height: 44px; justify-content: center; }
1797
+ }
1587
1798
 
1588
1799
  /* Xero › Import (server/ui/xero-import.js): Review compares one fact per row
1589
1800
  with ✓ or ≠ between the sides; the list and history tick rows for the bulk