@jabworks/condux 0.13.3 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jabworks/condux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "OpenCode plugin for the condux workflow toolkit — bundles the condux skills and injects the specialist agents (coder, explorer, planner, researcher) plus an opt-in plan-review listener.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -476,12 +476,21 @@
|
|
|
476
476
|
the smaller 0.25rem step now say so explicitly via --radius, which
|
|
477
477
|
already carries that value and is already this file's convention
|
|
478
478
|
(.nav .tabs, .popover, .chip, code, pre, …). */
|
|
479
|
+
/* Docket #48 / Q16 — three theme states, not two. These tokens are
|
|
480
|
+
dark-first like the core, so their light values are an override. Written
|
|
481
|
+
as a bare media query, clicking Dark on a light system flipped every core
|
|
482
|
+
token and left the highlight cream, because the media query still
|
|
483
|
+
matched. Written the way scripts/tokens/core.css:84-85,130 writes it:
|
|
484
|
+
the system-light block excludes an explicit dark stamp, and an explicit
|
|
485
|
+
light block beats a dark OS. */
|
|
479
486
|
:root {
|
|
480
487
|
--hl: rgba(255, 202, 22, 0.30); --hl-active: rgba(255, 202, 22, 0.55);
|
|
481
488
|
}
|
|
482
489
|
@media (prefers-color-scheme: light) {
|
|
483
|
-
:root { --hl: rgba(234, 179, 8, 0.42); --hl-active: rgba(202, 138, 4, 0.62); }
|
|
490
|
+
:root:not([data-theme="dark"]) { --hl: rgba(234, 179, 8, 0.42); --hl-active: rgba(202, 138, 4, 0.62); }
|
|
484
491
|
}
|
|
492
|
+
:root[data-theme="light"] { --hl: rgba(234, 179, 8, 0.42); --hl-active: rgba(202, 138, 4, 0.62); }
|
|
493
|
+
:root[data-theme="dark"] { --hl: rgba(255, 202, 22, 0.30); --hl-active: rgba(255, 202, 22, 0.55); }
|
|
485
494
|
* { box-sizing: border-box; }
|
|
486
495
|
html, body { height: 100%; }
|
|
487
496
|
/* .kit-error sets display:flex unconditionally; an author rule beats the
|
|
@@ -493,9 +502,16 @@
|
|
|
493
502
|
one legitimately 2-D layout in this file; everything else here is a
|
|
494
503
|
one-dimensional row or column and stays flex. */
|
|
495
504
|
body { margin: 0; background: var(--background); color: var(--foreground); font-family: var(--sans); font-size: var(--text-sm); line-height: var(--leading-normal); font-variant-numeric: tabular-nums; display: grid; grid-template-rows: 1fr auto; height: 100dvh; }
|
|
496
|
-
|
|
505
|
+
/* D10 — the document takes the width the two lists were spending. Every
|
|
506
|
+
column rule is scoped ABOVE the stacking breakpoint on purpose: an
|
|
507
|
+
unscoped `grid-template-columns` outranks the 860px and 1100px rules by
|
|
508
|
+
source order, and the three-column grid survives into a phone viewport.
|
|
509
|
+
Measured at 640px while building D10: `.main` collapsed to 88px and the
|
|
510
|
+
340px popover ran off the right edge of the screen. */
|
|
511
|
+
.app { display: grid; grid-template-columns: 236px minmax(0, 1fr) 330px; min-height: 0; }
|
|
512
|
+
.app > * { min-width: 0; } /* Q14 — a bare 1fr column floors at min-content */
|
|
497
513
|
@media (max-width: 1100px) {
|
|
498
|
-
.app { grid-template-columns: 208px minmax(0, 1fr)
|
|
514
|
+
.app { grid-template-columns: 208px minmax(0, 1fr) 300px; }
|
|
499
515
|
}
|
|
500
516
|
@media (max-width: 860px) {
|
|
501
517
|
.app { grid-template-columns: 1fr; grid-template-rows: auto 1fr auto; max-height: none; }
|
|
@@ -537,8 +553,12 @@
|
|
|
537
553
|
.badge-warning { background: var(--warning-muted); color: var(--warning); border-color: var(--warning-border); }
|
|
538
554
|
.badge-soft { background: var(--primary-muted); color: var(--primary-text); }
|
|
539
555
|
|
|
540
|
-
/* Left: navigation — Contents (docs + headings) | Files (paths touched)
|
|
541
|
-
|
|
556
|
+
/* Left: navigation — Contents (docs + headings) | Files (paths touched).
|
|
557
|
+
D10 — a table of contents rather than a menu: no card ground, no active
|
|
558
|
+
pill, depth as padding. Note `.nav a` is display:flex, so a negative
|
|
559
|
+
text-indent does not produce a hanging indent here — it shifts the flex
|
|
560
|
+
container's first line and clips the label against the row. */
|
|
561
|
+
.nav { border-right: 1px solid var(--border); background: var(--background); overflow-y: auto; padding: 22px 16px 40px; }
|
|
542
562
|
.nav .kit-theme { margin: 0 0 12px; }
|
|
543
563
|
.nav .tabs { display: flex; margin: 0 0 12px; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
|
|
544
564
|
.nav .tab { flex: 1; font: 500 var(--text-2xs) var(--sans); letter-spacing: .08em; text-transform: uppercase; padding: 6px 4px;
|
|
@@ -547,16 +567,16 @@
|
|
|
547
567
|
.nav .tab:hover { color: var(--foreground); }
|
|
548
568
|
.nav .tab.active { background: var(--accent); color: var(--foreground); }
|
|
549
569
|
.nav a.doc { font-weight: 600; color: var(--foreground); }
|
|
550
|
-
.nav a.doc.active {
|
|
570
|
+
.nav a.doc.active { color: var(--foreground); background: transparent; }
|
|
551
571
|
.nav a.file .t { font-family: var(--mono); font-size: var(--text-xs); }
|
|
552
572
|
.nav .none { color: var(--subtle); font-size: var(--text-xs); padding: 4px 6px; }
|
|
553
573
|
code.mention-flash { outline: 2px solid var(--warning); outline-offset: 1px; }
|
|
554
574
|
.nav .label, .chat .label { font-size: var(--text-2xs); letter-spacing: 0.08em; text-transform: uppercase; color: var(--subtle); margin: 0 0 12px; padding: 0 6px; }
|
|
555
|
-
.nav a { display: flex; align-items: center; gap: 6px; color: var(--muted-foreground); text-decoration: none; padding:
|
|
575
|
+
.nav a { display: flex; align-items: center; gap: 6px; color: var(--muted-foreground); text-decoration: none; padding: 3px 2px; border-radius: 0; border-left: none; }
|
|
556
576
|
.nav a span.t { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; }
|
|
557
|
-
.nav a:hover {
|
|
558
|
-
.nav a.active { color: var(--foreground);
|
|
559
|
-
.nav a.lvl2 { padding-left:
|
|
577
|
+
.nav a:hover { color: var(--foreground); }
|
|
578
|
+
.nav a.active { color: var(--foreground); font-weight: 600; background: transparent; }
|
|
579
|
+
.nav a.lvl2 { padding-left: 14px; } .nav a.lvl3 { padding-left: 26px; font-size: var(--text-xs); color: var(--subtle); }
|
|
560
580
|
|
|
561
581
|
/* Directory mode: doc-site tree — folder rows fold, filter narrows, the
|
|
562
582
|
active doc alone carries its headings. All of it DIRMODE-gated. */
|
|
@@ -566,14 +586,14 @@
|
|
|
566
586
|
.nav .tree-filter input:focus { border-color: var(--ring); }
|
|
567
587
|
.nav a.folder { font-weight: 600; color: var(--muted-foreground); }
|
|
568
588
|
.nav a.folder .arrow { flex: none; width: 12px; color: var(--subtle); font-size: var(--text-2xs); }
|
|
569
|
-
.crumbs { display: none; max-width:
|
|
589
|
+
.crumbs { display: none; max-width: 720px; margin: 0 auto 18px; font: var(--text-xs) var(--mono); color: var(--subtle);
|
|
570
590
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
571
591
|
.crumbs .seg { color: var(--muted-foreground); }
|
|
572
592
|
.crumbs a.seg { color: var(--muted-foreground); text-decoration: none; cursor: pointer; }
|
|
573
593
|
.crumbs a.seg:hover { color: var(--foreground); text-decoration: underline; }
|
|
574
594
|
.crumbs .sep { margin: 0 6px; color: var(--subtle); }
|
|
575
595
|
.crumbs .cur { color: var(--foreground); }
|
|
576
|
-
.pnav { display: none; max-width:
|
|
596
|
+
.pnav { display: none; max-width: 720px; margin: 32px auto 0; padding-top: 14px; border-top: 1px solid var(--border);
|
|
577
597
|
justify-content: space-between; gap: 12px; }
|
|
578
598
|
.pnav a { display: inline-flex; align-items: center; gap: 6px; max-width: 48%; color: var(--muted-foreground);
|
|
579
599
|
text-decoration: none; font-size: var(--text-sm); padding: 6px 10px; border: 1px solid var(--border); border-radius: var(--radius);
|
|
@@ -581,23 +601,23 @@
|
|
|
581
601
|
.pnav a:hover { background: var(--accent); color: var(--foreground); }
|
|
582
602
|
.pnav .spacer { flex: 1; }
|
|
583
603
|
|
|
584
|
-
/* Center: plan
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
.main .hint { color: var(--subtle); margin: 0 auto 24px; font-size: var(--text-xs); max-width:
|
|
591
|
-
.doc { max-width:
|
|
604
|
+
/* Center: plan. `position: relative` and `overflow-y: auto` are load-bearing
|
|
605
|
+
and must both stay: #popover is absolutely positioned against this box,
|
|
606
|
+
and selDoc stores coordinates relative to it plus its scrollTop. Moving
|
|
607
|
+
the scroll to body, or wrapping #plan in its own scroller, silently
|
|
608
|
+
breaks every annotation anchor. */
|
|
609
|
+
.main { overflow-y: auto; padding: 40px 44px 140px; position: relative; }
|
|
610
|
+
.main .hint { color: var(--subtle); margin: 0 auto 24px; font-size: var(--text-xs); max-width: 720px; }
|
|
611
|
+
.doc { max-width: 720px; margin: 0 auto; line-height: var(--leading-relaxed); counter-reset: hl; }
|
|
592
612
|
|
|
593
613
|
/* Revision diff — on a revised plan, show what changed instead of a silent swap. */
|
|
594
|
-
.revbar { display: none; align-items: center; gap: 10px; max-width:
|
|
614
|
+
.revbar { display: none; align-items: center; gap: 10px; max-width: 720px; margin: 0 auto 20px;
|
|
595
615
|
padding: 9px 12px; border: 1px solid var(--warning-border); background: var(--warning-muted); border-radius: var(--radius); }
|
|
596
616
|
.revbar.show { display: flex; }
|
|
597
617
|
.revbar .rb-text { color: var(--warning); font: 600 var(--text-xs) var(--sans); }
|
|
598
618
|
.revbar .rb-stat { color: var(--muted-foreground); font-size: var(--text-xs); }
|
|
599
619
|
.revbar .rb-actions { margin-left: auto; display: flex; gap: 6px; }
|
|
600
|
-
.diffwrap { display: none; max-width:
|
|
620
|
+
.diffwrap { display: none; max-width: 720px; margin: 0 auto; }
|
|
601
621
|
.diffwrap.show { display: block; }
|
|
602
622
|
.diffview { background: var(--muted); border: 1px solid var(--border); border-radius: var(--radius);
|
|
603
623
|
padding: 8px 0; overflow-x: auto; font: var(--text-xs)/var(--leading-normal) var(--mono); }
|
|
@@ -609,11 +629,24 @@
|
|
|
609
629
|
.diffline.del { background: var(--destructive-muted); color: var(--destructive); }
|
|
610
630
|
.diffline.del .g { color: var(--destructive); }
|
|
611
631
|
.diffline.fold { justify-content: center; color: var(--subtle); font-style: italic; padding: 3px 12px; }
|
|
632
|
+
/* D10 — a typographic spine. Hierarchy is space and rule, not boxes: h1
|
|
633
|
+
closes with a rule, h2 opens a section against a hairline, h3 hangs off
|
|
634
|
+
a short dash. This is what makes a long plan skimmable without folding
|
|
635
|
+
it — folds are ruled out on this surface (live Ranges and mark.hl nodes
|
|
636
|
+
have no client rects inside a collapsed <details>). */
|
|
612
637
|
h1, h2, h3, h4, h5, h6 { line-height: var(--leading-snug); margin: 1.5em 0 0.6em; scroll-margin-top: 24px; }
|
|
613
|
-
h1 { font-size: var(--text-
|
|
638
|
+
h1 { font-size: var(--text-2xl); letter-spacing: -0.015em; margin: 0 0 0.2em; padding-bottom: 0.5em; border-bottom: 1px solid var(--border); }
|
|
639
|
+
h2 { font-size: var(--text-lg); letter-spacing: -0.01em; margin: 2.4em 0 0.7em; padding-top: 0.9em; border-top: 1px solid var(--border); }
|
|
640
|
+
h3 { font-size: var(--text-base); color: var(--foreground); margin: 1.9em 0 0.5em; }
|
|
641
|
+
h3::before { content: ''; display: inline-block; width: 14px; height: 1px; vertical-align: middle; margin-right: 10px; background: var(--subtle); }
|
|
614
642
|
h4 { font-size: var(--text-sm); } h5, h6 { font-size: var(--text-xs); color: var(--muted-foreground); }
|
|
643
|
+
.doc > *:first-child, .docpane > *:first-child { margin-top: 0; }
|
|
615
644
|
p { margin: 0.6em 0; }
|
|
616
645
|
ul, ol { margin: 0.6em 0; padding-left: 22px; } li { margin: 0.25em 0; }
|
|
646
|
+
/* Q15 — capping measure on <p> alone misses the worst line, and a plan is
|
|
647
|
+
mostly list items. Table cells keep their column. */
|
|
648
|
+
.doc p, .doc li { max-width: 68ch; }
|
|
649
|
+
.doc td p, .doc td li, .doc th p { max-width: none; }
|
|
617
650
|
li.task { list-style: none; margin-left: -18px; }
|
|
618
651
|
del { color: var(--subtle); }
|
|
619
652
|
a { color: var(--info); }
|
|
@@ -622,12 +655,32 @@
|
|
|
622
655
|
pre code { background: none; border: none; padding: 0; }
|
|
623
656
|
blockquote { margin: 0.6em 0; padding: 2px 14px; color: var(--muted-foreground); border-left: 3px solid var(--primary); }
|
|
624
657
|
hr { border: none; border-top: 1px solid var(--border); margin: 1.6em 0; }
|
|
658
|
+
/* D10 — h2 now opens a section against its own rule, which makes a
|
|
659
|
+
preceding `---` a second divider saying the same thing. draft-plan's
|
|
660
|
+
canonical template puts `---` immediately before every task card, so
|
|
661
|
+
this is the shape of nearly every plan this surface renders: measured
|
|
662
|
+
9 doubled rules in one 9-task plan. Each block is wrapped in its own
|
|
663
|
+
.blk, so `hr + h2` cannot match — the relative selector looks forward
|
|
664
|
+
to the next block instead. The h2's rule is the spine and stays; the
|
|
665
|
+
redundant separator goes. */
|
|
666
|
+
.blk:has(> hr):has(+ .blk h2) { display: none; }
|
|
625
667
|
table { border-collapse: collapse; margin: 0.9em 0; width: 100%; display: block; overflow-x: auto; font-size: var(--text-sm); }
|
|
626
668
|
th, td { border: 1px solid var(--border); padding: 6px 10px; text-align: left; vertical-align: top; }
|
|
627
669
|
th { background: var(--muted); font-weight: 600; }
|
|
628
670
|
tbody tr:nth-child(even) { background: color-mix(in oklch, var(--muted) 40%, transparent); }
|
|
629
671
|
.blk { border-radius: 4px; padding: 1px 8px; margin: 0 -8px; }
|
|
630
|
-
|
|
672
|
+
/* D10 — numbered marginalia. The counter increments per highlight in
|
|
673
|
+
document order and the review column repeats the same ordinal in its
|
|
674
|
+
gutter, so the two columns read as one artifact. paint() orders the
|
|
675
|
+
thread by document position to keep the two numberings in agreement —
|
|
676
|
+
without that they diverge the moment a note is added above an earlier
|
|
677
|
+
one. The counter is also the only trace of an annotation on paper, since
|
|
678
|
+
the review column is print-suppressed. */
|
|
679
|
+
mark.hl { background: var(--hl); color: inherit; border-radius: 2px; cursor: pointer; padding: 0.1em 1px;
|
|
680
|
+
box-decoration-break: clone; -webkit-box-decoration-break: clone; counter-increment: hl; }
|
|
681
|
+
mark.hl::after { content: counter(hl); font: 600 var(--text-2xs) var(--mono); color: var(--muted-foreground);
|
|
682
|
+
vertical-align: super; margin-left: 2px; line-height: 0; }
|
|
683
|
+
mark.hl:hover { background: var(--hl-active); }
|
|
631
684
|
mark.hl.active { background: var(--hl-active); box-shadow: 0 0 0 1px var(--warning-border); }
|
|
632
685
|
|
|
633
686
|
/* Floating "comment" button (Button surface) + comment Popover */
|
|
@@ -664,20 +717,51 @@
|
|
|
664
717
|
.popover .ta { min-height: 132px; border: none; border-radius: 0; padding: 8px 10px; } .popover .ta:focus-visible { outline: none; box-shadow: none; }
|
|
665
718
|
.composer .ta { min-height: 52px; }
|
|
666
719
|
|
|
667
|
-
/* Right: annotations
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
720
|
+
/* Right: annotations as marginalia + composer + decisions.
|
|
721
|
+
D10 — the review column recedes so the document can lead. Notes lose
|
|
722
|
+
their card chrome and are separated by rule alone, each keyed by the
|
|
723
|
+
same ordinal its highlight carries in the document. */
|
|
724
|
+
.chat { border-left: 1px solid var(--border); background: var(--background); display: flex; flex-direction: column; }
|
|
725
|
+
.chat .label { padding: 22px 18px 0; margin-bottom: 4px; display: flex; align-items: center; gap: 8px;
|
|
726
|
+
font: 600 var(--text-2xs) var(--sans); letter-spacing: .09em; text-transform: uppercase; color: var(--subtle); }
|
|
727
|
+
.thread { flex: 1; overflow-y: auto; padding: 10px 18px; display: flex; flex-direction: column; gap: 0; counter-reset: note; }
|
|
671
728
|
.thread .empty { color: var(--subtle); padding: 8px 4px; }
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
.msg
|
|
675
|
-
.msg
|
|
729
|
+
.msg { position: relative; background: transparent; border: none; border-top: 1px solid var(--border);
|
|
730
|
+
border-left: 2px solid transparent; border-radius: 0; padding: 14px 0 14px 26px; box-shadow: none; cursor: default; }
|
|
731
|
+
.msg:first-child { border-top: none; }
|
|
732
|
+
.msg::before { counter-increment: note; content: counter(note); position: absolute; left: 8px; top: 15px;
|
|
733
|
+
font: 600 var(--text-2xs) var(--mono); color: var(--subtle); }
|
|
734
|
+
/* A note on another document takes no ordinal — see paint(). The marker
|
|
735
|
+
says "annotated, elsewhere" without claiming a position in a sequence
|
|
736
|
+
the reader cannot see. */
|
|
737
|
+
.msg[data-other-doc]::before { counter-increment: none; content: '·'; }
|
|
738
|
+
.msg.note { cursor: pointer; }
|
|
739
|
+
.msg.note.active { background: var(--accent); }
|
|
676
740
|
.msg .meta { display: flex; justify-content: space-between; gap: 8px; margin-bottom: 4px; align-items: center; }
|
|
677
|
-
.msg .who { color: var(--muted-foreground); font
|
|
741
|
+
.msg .who { color: var(--muted-foreground); font: 600 var(--text-2xs) var(--sans); text-transform: uppercase; letter-spacing: .07em; }
|
|
678
742
|
.msg .del { cursor: pointer; border: none; background: none; color: var(--subtle); font-size: var(--text-2xs); padding: 0 2px; } .msg .del:hover { color: var(--destructive); }
|
|
679
|
-
.msg .quote { color: var(--muted-foreground); font-size: var(--text-2xs); border-left: 2px solid var(--
|
|
680
|
-
.msg .body { white-space: pre-wrap; word-break: break-word; color: var(--
|
|
743
|
+
.msg .quote { color: var(--muted-foreground); font-size: var(--text-2xs); font-style: italic; border-left: 2px solid var(--hl-active); padding: 1px 9px; margin-bottom: 6px; max-height: 42px; overflow: hidden; }
|
|
744
|
+
.msg .body { white-space: pre-wrap; word-break: break-word; color: var(--foreground); }
|
|
745
|
+
|
|
746
|
+
/* Category as colour, carried by the two marks already in the gutter — the
|
|
747
|
+
ordinal and the label — rather than by a card edge. Lets you count the
|
|
748
|
+
issues in a full thread without reading it. `data-cat` is set in paint();
|
|
749
|
+
the category is otherwise only written as TEXT into .who, and CSS cannot
|
|
750
|
+
select on text content. */
|
|
751
|
+
/* The five categories are exactly the popover's chips — Comment, Issue,
|
|
752
|
+
Question, Suggestion, Nitpick — plus "Note", which is what a note gets
|
|
753
|
+
when no chip was clicked (openToolbar clears pendingCat on every open,
|
|
754
|
+
so the markup's initial `active` chip is not a default). Comment and
|
|
755
|
+
Note are deliberately quiet: they are the common case, and colouring
|
|
756
|
+
them would leave nothing for the categories that carry a decision. */
|
|
757
|
+
.msg[data-cat="Issue"] { border-left-color: var(--destructive); }
|
|
758
|
+
.msg[data-cat="Issue"]::before, .msg[data-cat="Issue"] .who { color: var(--destructive); }
|
|
759
|
+
.msg[data-cat="Question"] { border-left-color: var(--warning); }
|
|
760
|
+
.msg[data-cat="Question"]::before, .msg[data-cat="Question"] .who { color: var(--warning); }
|
|
761
|
+
.msg[data-cat="Suggestion"] { border-left-color: var(--primary); }
|
|
762
|
+
.msg[data-cat="Suggestion"]::before, .msg[data-cat="Suggestion"] .who { color: var(--primary-text); }
|
|
763
|
+
.msg[data-cat="Nitpick"] { border-left-color: var(--subtle); }
|
|
764
|
+
.msg[data-cat="Nitpick"]::before, .msg[data-cat="Nitpick"] .who { color: var(--subtle); }
|
|
681
765
|
|
|
682
766
|
/* Terminus: Separator + composer */
|
|
683
767
|
.composer { border-top: 1px solid var(--border); padding: 12px 14px; }
|
|
@@ -709,6 +793,22 @@
|
|
|
709
793
|
.sent .again { margin-top: 10px; }
|
|
710
794
|
.dot { width: 7px; height: 7px; border-radius: 9999px; background: currentColor; animation: pulse 1.3s ease-in-out infinite; }
|
|
711
795
|
@keyframes pulse { 0%, 100% { opacity: .3; } 50% { opacity: 1; } }
|
|
796
|
+
|
|
797
|
+
/* Docket #50 — the shell has to COLLAPSE for print, not just hide its
|
|
798
|
+
chrome. The kit's print block sets [data-kit-chrome] { display: none },
|
|
799
|
+
which hides .nav and .chat but leaves their grid TRACKS on .app, so
|
|
800
|
+
.main auto-placed into the first (248px) track and the plan printed in a
|
|
801
|
+
~151px column. .main is also the scroll container, so printed output was
|
|
802
|
+
clipped to the one screenful scrolled into view — the rest of the plan
|
|
803
|
+
did not print at all.
|
|
804
|
+
The measure cap stays: prose set to the full page width is no more
|
|
805
|
+
readable on paper than on screen. Only the centring margin goes, so the
|
|
806
|
+
column sits against the page's own margin. */
|
|
807
|
+
@media print {
|
|
808
|
+
.app { display: block; }
|
|
809
|
+
.main { overflow: visible; padding: 0; height: auto; }
|
|
810
|
+
.doc, .crumbs { margin: 0; }
|
|
811
|
+
}
|
|
712
812
|
</style>
|
|
713
813
|
</head>
|
|
714
814
|
<body>
|
|
@@ -1473,12 +1573,47 @@
|
|
|
1473
1573
|
function sendChat(){ const ta=document.getElementById('input'); const text=ta.value.trim(); if(!text)return;
|
|
1474
1574
|
annotations.push({id:nextId++,kind:'chat',quote:'',text:text,section:''}); ta.value=''; paint(); }
|
|
1475
1575
|
|
|
1576
|
+
// The thread renders in DOCUMENT order, not insertion order, because each
|
|
1577
|
+
// highlight carries a CSS counter numbered by document position and the
|
|
1578
|
+
// gutter repeats that ordinal. Left in insertion order the two numberings
|
|
1579
|
+
// disagree the moment a note is added above an earlier one.
|
|
1580
|
+
//
|
|
1581
|
+
// Sort a COPY: `annotations` is the identity store that saveNote pushes to
|
|
1582
|
+
// and the delete handler indexes into with indexOf, so reordering it in
|
|
1583
|
+
// place breaks both. And never reorder the rendered DOM instead — chat
|
|
1584
|
+
// messages have no document position and would be shuffled to the top.
|
|
1585
|
+
function threadOrder(){
|
|
1586
|
+
// Scope the mark lookup to the ACTIVE pane. In DIRMODE every doc's pane
|
|
1587
|
+
// stays in the DOM hidden, so a document-wide query happily matches a
|
|
1588
|
+
// mark in another document's pane and sorts against it.
|
|
1589
|
+
const pane=docState[activeDoc]&&docState[activeDoc].pane;
|
|
1590
|
+
const markOf=function(a){ return pane?pane.querySelector('mark.hl[data-aid="'+a.id+'"]'):null; };
|
|
1591
|
+
// Array.prototype.sort is stable, so a 0 comparison keeps chat messages
|
|
1592
|
+
// — and any note whose mark is missing — in their existing order.
|
|
1593
|
+
return annotations.slice().sort(function(x,y){
|
|
1594
|
+
if(x.kind!=='note'||y.kind!=='note') return 0;
|
|
1595
|
+
const mx=markOf(x), my=markOf(y);
|
|
1596
|
+
if(!mx||!my) return 0;
|
|
1597
|
+
return (mx.compareDocumentPosition(my)&Node.DOCUMENT_POSITION_FOLLOWING)?-1:1;
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1476
1600
|
function paint(){
|
|
1477
1601
|
const box=document.getElementById('thread'); box.innerHTML='';
|
|
1478
1602
|
document.getElementById('count').textContent=annotations.length?'('+annotations.length+')':'';
|
|
1479
1603
|
if(!annotations.length){ box.innerHTML='<div class="empty">No notes yet. Select text to comment, or send a message.</div>'; updateBadges(); updateSummary(); return; }
|
|
1480
|
-
|
|
1604
|
+
threadOrder().forEach(function(a){
|
|
1481
1605
|
const m=document.createElement('div'); m.className='msg'+(a.kind==='note'?' note':''); if(a.kind==='note')m.dataset.aid=String(a.id);
|
|
1606
|
+
// The category is otherwise written only as TEXT into .who, and CSS
|
|
1607
|
+
// cannot select on text content — this is what the colour rules key on.
|
|
1608
|
+
m.dataset.cat=(a.kind==='note'?(a.cat||'Note'):'message');
|
|
1609
|
+
// DIRMODE: the thread lists notes from EVERY document, but the in-document
|
|
1610
|
+
// ordinal counts only the visible pane — marks inside a display:none pane
|
|
1611
|
+
// do not increment a CSS counter. Numbering every row would therefore
|
|
1612
|
+
// label a note "3" in the gutter whose highlight reads "1" in the
|
|
1613
|
+
// document, which is exactly the divergence the numbering exists to
|
|
1614
|
+
// remove. Rows belonging to another document are flagged so they take no
|
|
1615
|
+
// ordinal, leaving both sequences running 1..m over the same set.
|
|
1616
|
+
if(a.kind==='note'&&a.doc&&a.doc!==activeDoc) m.dataset.otherDoc='1';
|
|
1482
1617
|
const meta=document.createElement('div'); meta.className='meta';
|
|
1483
1618
|
const who=document.createElement('span'); who.className='who'; who.textContent=a.kind==='note'?(a.cat||'Note'):'message';
|
|
1484
1619
|
const del=document.createElement('button'); del.className='del'; del.textContent='×';
|
|
@@ -1552,7 +1687,12 @@
|
|
|
1552
1687
|
if(submitting) return; submitting=true;
|
|
1553
1688
|
document.querySelectorAll('.dbar button').forEach(function(b){b.disabled=true;});
|
|
1554
1689
|
try{
|
|
1555
|
-
|
|
1690
|
+
// Send the thread in the SAME order the reviewer saw it. The rendered
|
|
1691
|
+
// thread is ordered by document position and each note is numbered in
|
|
1692
|
+
// the gutter; posting the raw insertion-order array would hand the
|
|
1693
|
+
// agent a different numbering than the one on screen, so "note 1" in a
|
|
1694
|
+
// conversation would mean two different notes.
|
|
1695
|
+
const r=await fetch('/api/feedback',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({decision:decision,thread:threadOrder()})});
|
|
1556
1696
|
if(!r.ok) throw new Error('status '+r.status);
|
|
1557
1697
|
hideApiError();
|
|
1558
1698
|
decisionSent=true; // a terminal decision makes the server exit —
|