portable-agent-layer 0.34.0 → 0.35.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.
@@ -103,6 +103,8 @@ Exit codes: `0` = clean, `1` = errors found (or warnings under `--strict`), `2`
103
103
 
104
104
  Open `<out>/<deck-name>/<deck-name>.html` in your browser. Iterate by editing a slide, re-running the build, and refreshing the tab. Reveal shortcuts: `F` = fullscreen, `S` = speaker notes window, `?` = keyboard shortcuts, `Esc` = overview.
105
105
 
106
+ **Print / PDF view with trainer notes.** Open the built HTML in your browser and press Cmd/Ctrl-P (no special URL param needed). The print stream interleaves trainer notes between slides — `[Slide 1][Notes 1][Slide 2][Notes 2]…` — so flipping pages reads in delivery order. Long notes flow across multiple pages. Slides without a `Note:` block produce no extra page. Each notes page carries a header identifying the source slide. Limitation: a single fenced code block inside notes cannot break across pages — the doctor warns at 30 lines (`notes-code-too-long`); split the block or shorten the example.
107
+
106
108
  ## Deck folder layout
107
109
 
108
110
  ```
@@ -9,4 +9,30 @@ The default. Title at top, body below.
9
9
  - And this
10
10
  - **Bold**, *italic*, `inline code`, [links](https://example.com)
11
11
 
12
- Note: If you don't add a `data-layout` directive, this is what you get.
12
+ Note:
13
+ - [Reveal.js print docs](https://revealjs.com/pdf-export/)
14
+ - Why this slide demonstrates print-with-notes
15
+ - Long enough to cross a page break in the trainer print-out
16
+ - Mixes link, bullet hierarchy, blockquote, inline code
17
+ - Lets you verify the multi-page flow without authoring a fixture deck
18
+ - Default layout — when to leave it off
19
+ - You want title + body, no special shape
20
+ - You want the layout-agnostic spacing rules to kick in
21
+ - You want the doctor to skip layout-shape checks
22
+ - Bullet hygiene reminder (per SKILL.md)
23
+ - 2–15 words at top level, 2–10 at sub
24
+ - No prose paragraphs — the `prose-paragraph-in-body` rule will warn
25
+ - Em-dash continuation in bullets is also flagged
26
+ - Quote example
27
+ > Slides are a delivery surface, not a document. Prose belongs in notes.
28
+ - Anticipated questions
29
+ - "Why a 30-line cap on notes code blocks?" — single `<pre>` blocks don't break across printed pages; longer blocks clip
30
+ - "Can I disable the trainer-notes printout?" — yes, remove the notes injection block from the `.then()` in skeleton.html in your local override
31
+ - "Does the speaker view (S key) still work?" — yes, the injection only affects print, not the speaker view path
32
+ - Forward references
33
+ - more on layout-specific rules in the `code` and `table` slides
34
+ - more on print-view polish in `base.css` under "Trainer notes in print"
35
+ - Closing beats
36
+ - The print-with-notes feature exists so a trainer can hand a delegate a printed deck and a printed prep packet at once
37
+ - Ordering is `[Slide 1][Notes 1][Slide 2][Notes 2]…` so flipping pages reads in delivery order
38
+ - Slides without a `Note:` block produce zero extra pages — title cards stay clean
@@ -436,3 +436,209 @@ html.print-pdf .reveal .slides .pdf-page {
436
436
  margin: 16px auto !important;
437
437
  }
438
438
  }
439
+
440
+ /* ── Trainer notes in print (Cmd+P on the regular URL)
441
+ * skeleton.html injects a `.print-notes-page` div after each slide section that
442
+ * has speaker notes. On screen they are invisible; Cmd+P produces the ordering
443
+ * [Slide N][Notes N][Slide N+1][Notes N+1]…. Long notes flow across multiple
444
+ * pages. Slides with no Note: block produce no extra page.
445
+ *
446
+ * Key mechanics:
447
+ * • `page-break-after: always` on sections forces each slide to end at a page
448
+ * boundary even if its content is shorter than the page height.
449
+ * • `page-break-before/after: always` on .print-notes-page ensures the notes
450
+ * block starts on a fresh page and the next slide starts on a fresh page.
451
+ * • `break-inside: auto` lets long notes flow across pages naturally. */
452
+ @media print {
453
+ html:not(.print-pdf) .reveal .slides > section {
454
+ page-break-after: always !important;
455
+ break-after: always !important;
456
+ }
457
+
458
+ html:not(.print-pdf) .reveal .slides > .print-notes-page {
459
+ display: block !important;
460
+ page-break-before: always !important;
461
+ break-before: always !important;
462
+ page-break-after: always !important;
463
+ break-after: always !important;
464
+ page-break-inside: auto;
465
+ break-inside: auto;
466
+ overflow: visible !important;
467
+ position: static !important;
468
+ min-height: 0 !important;
469
+ padding: 36px 56px 56px 56px;
470
+ box-sizing: border-box;
471
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
472
+ "Helvetica Neue", Arial, sans-serif;
473
+ font-size: 13pt;
474
+ line-height: 1.5;
475
+ color: #111;
476
+ background: #fff;
477
+ }
478
+
479
+ html:not(.print-pdf) .reveal .slides > .print-notes-page ul,
480
+ html:not(.print-pdf) .reveal .slides > .print-notes-page ol {
481
+ margin: 0.4em 0 0.8em 0;
482
+ padding-left: 1.4em;
483
+ }
484
+ html:not(.print-pdf) .reveal .slides > .print-notes-page li {
485
+ margin: 0.15em 0;
486
+ }
487
+ html:not(.print-pdf) .reveal .slides > .print-notes-page a {
488
+ color: var(--brand-primary, #0040c2);
489
+ text-decoration: underline;
490
+ }
491
+ html:not(.print-pdf) .reveal .slides > .print-notes-page code {
492
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
493
+ font-size: 0.92em;
494
+ background: #f4f4f4;
495
+ padding: 1px 4px;
496
+ border-radius: 3px;
497
+ }
498
+ html:not(.print-pdf) .reveal .slides > .print-notes-page pre {
499
+ font-size: 0.92em;
500
+ line-height: 1.4;
501
+ background: #f4f4f4;
502
+ padding: 8px 12px;
503
+ border-radius: 4px;
504
+ white-space: pre-wrap;
505
+ overflow: visible;
506
+ }
507
+ html:not(.print-pdf) .reveal .slides > .print-notes-page blockquote {
508
+ margin: 0.6em 0 0.6em 1em;
509
+ padding-left: 0.8em;
510
+ border-left: 2px solid #a0a0a0;
511
+ color: #444;
512
+ font-style: italic;
513
+ }
514
+
515
+ /* Slide-context header: "Notes — slide N <slide title>" */
516
+ html:not(.print-pdf) .reveal .slides > .print-notes-page .print-notes-header {
517
+ display: flex;
518
+ align-items: baseline;
519
+ gap: 0.8em;
520
+ margin: 0 0 24px 0;
521
+ padding: 0 0 10px 0;
522
+ border-bottom: 1.5pt solid var(--brand-primary, #0040c2);
523
+ -webkit-print-color-adjust: exact;
524
+ print-color-adjust: exact;
525
+ }
526
+ html:not(.print-pdf) .reveal .slides > .print-notes-page .print-notes-label {
527
+ font-size: 11pt;
528
+ font-weight: 600;
529
+ text-transform: uppercase;
530
+ letter-spacing: 0.05em;
531
+ color: var(--brand-primary, #0040c2);
532
+ white-space: nowrap;
533
+ }
534
+ html:not(.print-pdf) .reveal .slides > .print-notes-page .print-notes-title {
535
+ font-size: 13pt;
536
+ font-weight: 500;
537
+ color: #222;
538
+ }
539
+ }
540
+
541
+ /* ── Per-slide print chrome (logo + slide number)
542
+ * skeleton.html injects `.print-logo` and `.print-slide-number` into every
543
+ * section. On screen: invisible. In Cmd+P print: positioned at the same
544
+ * bottom corners as the screen-mode chrome, once per slide page.
545
+ *
546
+ * The screen-mode equivalents (.slides::after logo and .slides > .slide-number)
547
+ * are suppressed in print because they are positioned relative to .slides — a
548
+ * container that spans all pages in Cmd+P mode, putting them at the very end
549
+ * of the document rather than on each individual slide. */
550
+ @media print {
551
+ /* Suppress the screen-mode global chrome */
552
+ html:not(.print-pdf) .reveal .slides::after,
553
+ html:not(.print-pdf) .reveal .slides > .slide-number {
554
+ display: none !important;
555
+ }
556
+
557
+ /* Sections must be position:relative so absolute children anchor to the
558
+ * slide page, not to the full-document .slides container. */
559
+ html:not(.print-pdf) .reveal .slides > section {
560
+ position: relative !important;
561
+ /* Ensure background images (logos) print on all browsers. */
562
+ -webkit-print-color-adjust: exact !important;
563
+ print-color-adjust: exact !important;
564
+ }
565
+
566
+ html:not(.print-pdf) .reveal .slides > section .print-logo {
567
+ display: block !important;
568
+ position: absolute;
569
+ bottom: 1.5rem;
570
+ right: 1.5rem;
571
+ width: 90px;
572
+ height: 28px;
573
+ background: var(--brand-logo) no-repeat right bottom / contain;
574
+ opacity: 0.55;
575
+ pointer-events: none;
576
+ -webkit-print-color-adjust: exact;
577
+ print-color-adjust: exact;
578
+ }
579
+
580
+ html:not(.print-pdf) .reveal .slides > section .print-slide-number {
581
+ display: block !important;
582
+ position: absolute;
583
+ bottom: 1.5rem;
584
+ left: 1.5rem;
585
+ font-family: var(--font-body, sans-serif);
586
+ font-size: 9pt;
587
+ font-weight: 400;
588
+ line-height: 28px;
589
+ color: #71717A;
590
+ letter-spacing: 0.05em;
591
+ pointer-events: none;
592
+ }
593
+ }
594
+
595
+ /* ── Layout-specific print overrides
596
+ * Reveal's vendored print CSS forces several properties with !important on
597
+ * heading and paragraph elements, which clobbers layout-specific design intent:
598
+ * • h1-h6: font-size (28–20pt), color (#000), text-align (left),
599
+ * letter-spacing (normal), line-height (normal)
600
+ * • p, li, td: font-size (20pt), color (#000)
601
+ *
602
+ * We beat these by pairing higher-specificity selectors with !important.
603
+ * Reveal's rules are scoped to `html:not(.print-pdf) .reveal h1` — specificity
604
+ * (0,2,1). Our layout-scoped rules use `html:not(.print-pdf) .reveal
605
+ * section[data-layout="…"] h1` — specificity (0,3,2), which wins. */
606
+ @media print {
607
+ /* big-stat — restore the dominant number that Reveal shrinks to 28pt */
608
+ html:not(.print-pdf) .reveal section[data-layout="big-stat"] h1 {
609
+ font-size: 96pt !important;
610
+ color: var(--brand-primary, #0E1335) !important;
611
+ text-align: center !important;
612
+ letter-spacing: -0.025em !important;
613
+ line-height: 0.9 !important;
614
+ font-weight: 800 !important;
615
+ -webkit-print-color-adjust: exact !important;
616
+ print-color-adjust: exact !important;
617
+ }
618
+ html:not(.print-pdf) .reveal section[data-layout="big-stat"] h2,
619
+ html:not(.print-pdf) .reveal section[data-layout="big-stat"] p {
620
+ color: #71717A !important;
621
+ text-align: center !important;
622
+ font-weight: 400 !important;
623
+ }
624
+
625
+ /* quote / pull-quote — Reveal forces p { font-size: 20pt !important }, but
626
+ * blockquote text lives in p elements. `inherit` re-derives the size from the
627
+ * blockquote element (var(--text-xl) / var(--text-2xl)), restoring design intent. */
628
+ html:not(.print-pdf) .reveal section[data-layout="quote"] blockquote p,
629
+ html:not(.print-pdf) .reveal section[data-layout="pull-quote"] blockquote p {
630
+ font-size: inherit !important;
631
+ color: inherit !important;
632
+ }
633
+ }
634
+
635
+ /* Screen: injected notes pages are invisible. The `@media print` block above
636
+ * restores display:block. Using !important because Reveal may globally set
637
+ * visibility rules on .slides children. */
638
+ @media screen {
639
+ .reveal .slides > .print-notes-page,
640
+ .reveal .slides > section .print-logo,
641
+ .reveal .slides > section .print-slide-number {
642
+ display: none !important;
643
+ }
644
+ }
@@ -47,6 +47,55 @@ Reveal.initialize({
47
47
  const sn = document.querySelector('.reveal > .slide-number');
48
48
  const slides = document.querySelector('.reveal .slides');
49
49
  if (sn && slides) slides.appendChild(sn);
50
+
51
+ // Inject print-only chrome (logo + slide number) into every section, and a
52
+ // `.print-notes-page` after each section that has notes. All injected elements
53
+ // are display:none on screen; Cmd+P produces [Slide N][Notes N]… ordering.
54
+ const sections = document.querySelectorAll('.reveal .slides > section');
55
+ const totalSlides = sections.length;
56
+ let slideNum = 0;
57
+ for (const section of sections) {
58
+ slideNum++;
59
+
60
+ const logoEl = document.createElement('div');
61
+ logoEl.className = 'print-logo';
62
+ section.appendChild(logoEl);
63
+
64
+ const numEl = document.createElement('div');
65
+ numEl.className = 'print-slide-number';
66
+ numEl.textContent = `${slideNum} / ${totalSlides}`;
67
+ section.appendChild(numEl);
68
+
69
+ const notesEl = section.querySelector('aside.notes');
70
+ if (!notesEl?.innerHTML.trim()) continue;
71
+
72
+ const heading = section.querySelector('h1, h2, h3');
73
+ const title = heading ? heading.textContent.trim() : '';
74
+
75
+ const page = document.createElement('div');
76
+ page.className = 'print-notes-page';
77
+
78
+ const hdr = document.createElement('header');
79
+ hdr.className = 'print-notes-header';
80
+ const label = document.createElement('span');
81
+ label.className = 'print-notes-label';
82
+ label.textContent = `Notes — slide ${slideNum}`;
83
+ hdr.appendChild(label);
84
+ if (title) {
85
+ const titleSpan = document.createElement('span');
86
+ titleSpan.className = 'print-notes-title';
87
+ titleSpan.textContent = title;
88
+ hdr.appendChild(titleSpan);
89
+ }
90
+ page.appendChild(hdr);
91
+
92
+ const body = document.createElement('div');
93
+ body.className = 'print-notes-content';
94
+ body.innerHTML = notesEl.innerHTML;
95
+ page.appendChild(body);
96
+
97
+ section.insertAdjacentElement('afterend', page);
98
+ }
50
99
  });
51
100
  </script>
52
101
  </body>
@@ -632,6 +632,31 @@ export const RULES: Rule[] = [
632
632
  },
633
633
  },
634
634
 
635
+ {
636
+ // Notes get printed as separate pages with `showNotes: 'separate-page'`.
637
+ // Browsers split long *list* content across pages cleanly, but a single
638
+ // fenced code block inside notes is one indivisible element — if it runs
639
+ // longer than one printed page it gets clipped. Warn at 30 lines so the
640
+ // author can split or shorten before the trainer discovers it on paper.
641
+ name: "notes-code-too-long",
642
+ scope: "slide",
643
+ check: (ctx) => {
644
+ const notes = extractNotes(ctx.body);
645
+ if (!notes.trim()) return [];
646
+ const findings: Finding[] = [];
647
+ for (const n of codeBlockLineCounts(notes)) {
648
+ if (n > 30) {
649
+ findings.push({
650
+ rule: "notes-code-too-long",
651
+ severity: "W",
652
+ msg: `notes code block has ${n} lines — won't break across printed pages cleanly (limit 30)`,
653
+ });
654
+ }
655
+ }
656
+ return findings;
657
+ },
658
+ },
659
+
635
660
  // ── Deck-scope rules (Tier 3) ───────────────────────────────────────────
636
661
 
637
662
  {
@@ -55,6 +55,7 @@ Start your response with the following header in this mode:
55
55
 
56
56
  - **Mandatory output format** — Every response MUST use exactly one of the output formats above (ALGORITHM, NATIVE, or MINIMAL). No freeform output.
57
57
  - **Response format before questions** — Always complete the current response format output FIRST, then invoke AskUserQuestion at the end.
58
+ - **Named project → resume first** — Whenever the user references a project by name in ANY mode, run `bun ~/.pal/tools/project.ts resume <slug>` before any file search or task execution. The project record has paths, context, and handoff notes that make manual searching unnecessary.
58
59
 
59
60
  ---
60
61
 
@@ -73,7 +74,7 @@ Load context on-demand by reading the file at the path listed. Only load what th
73
74
  | Opinion tracking | `~/.pal/docs/OPINION_TRACKING.md` |
74
75
  | Steering rules | `~/.pal/docs/STEERING_RULES.md` |
75
76
  | Algorithm (complex work phases) | `~/.pal/docs/ALGORITHM.md` |
76
- | Project lifecycle (when/how to register and manage user projects) | `~/.pal/skills/projects/SKILL.md` |
77
+ | Project lookup AND lifecycle (find paths, resume state, register, manage) | `~/.pal/skills/projects/SKILL.md` |
77
78
 
78
79
  ### User Context (TELOS)
79
80
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {