heyiam 0.3.6 → 0.3.9
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/dist/archive.js +28 -0
- package/dist/db.js +9 -0
- package/dist/export.js +3 -0
- package/dist/llm/project-enhance.js +11 -5
- package/dist/mount.js +43 -18
- package/dist/public/assets/index-ByoBtx7P.css +1 -0
- package/dist/public/assets/index-LQHTU1Wz.js +37 -0
- package/dist/public/index.html +2 -2
- package/dist/render/build-render-data.js +1 -0
- package/dist/render/liquid.js +14 -1
- package/dist/render/mock-data.js +6 -0
- package/dist/render/select-profile-skills.js +54 -0
- package/dist/render/templates/aurora/portfolio.liquid +4 -4
- package/dist/render/templates/aurora/styles.css +6 -6
- package/dist/render/templates/bauhaus/portfolio.liquid +4 -4
- package/dist/render/templates/bauhaus/project.liquid +2 -2
- package/dist/render/templates/bauhaus/styles.css +1 -1
- package/dist/render/templates/blueprint/portfolio.liquid +4 -4
- package/dist/render/templates/blueprint/styles.css +1 -1
- package/dist/render/templates/canvas/portfolio.liquid +4 -4
- package/dist/render/templates/canvas/styles.css +2 -2
- package/dist/render/templates/carbon/portfolio.liquid +4 -4
- package/dist/render/templates/carbon/styles.css +7 -7
- package/dist/render/templates/chalk/portfolio.liquid +4 -4
- package/dist/render/templates/chalk/styles.css +44 -2
- package/dist/render/templates/circuit/portfolio.liquid +4 -4
- package/dist/render/templates/cosmos/portfolio.liquid +4 -4
- package/dist/render/templates/cosmos/styles.css +4 -4
- package/dist/render/templates/daylight/portfolio.liquid +4 -4
- package/dist/render/templates/editorial/portfolio.liquid +4 -4
- package/dist/render/templates/editorial/project.liquid +1 -1
- package/dist/render/templates/editorial/styles.css +6 -1
- package/dist/render/templates/ember/portfolio.liquid +4 -4
- package/dist/render/templates/ember/styles.css +2 -2
- package/dist/render/templates/glacier/portfolio.liquid +4 -4
- package/dist/render/templates/glacier/project.liquid +3 -3
- package/dist/render/templates/glacier/styles.css +2 -2
- package/dist/render/templates/grid/portfolio.liquid +4 -4
- package/dist/render/templates/grid/styles.css +2 -2
- package/dist/render/templates/kinetic/portfolio.liquid +4 -4
- package/dist/render/templates/kinetic/project.liquid +1 -1
- package/dist/render/templates/kinetic/styles.css +5 -5
- package/dist/render/templates/meridian/portfolio.liquid +4 -4
- package/dist/render/templates/meridian/styles.css +1 -1
- package/dist/render/templates/minimal/portfolio.liquid +3 -3
- package/dist/render/templates/minimal/project.liquid +1 -1
- package/dist/render/templates/mono/portfolio.liquid +4 -4
- package/dist/render/templates/mono/styles.css +15 -1
- package/dist/render/templates/neon/portfolio.liquid +4 -4
- package/dist/render/templates/neon/styles.css +11 -20
- package/dist/render/templates/noir/portfolio.liquid +4 -4
- package/dist/render/templates/noir/styles.css +5 -5
- package/dist/render/templates/obsidian/portfolio.liquid +4 -4
- package/dist/render/templates/obsidian/styles.css +2 -2
- package/dist/render/templates/paper/portfolio.liquid +4 -4
- package/dist/render/templates/paper/project.liquid +2 -2
- package/dist/render/templates/paper/styles.css +60 -1
- package/dist/render/templates/parallax/portfolio.liquid +4 -4
- package/dist/render/templates/parallax/styles.css +1 -1
- package/dist/render/templates/parchment/portfolio.liquid +4 -4
- package/dist/render/templates/parchment/styles.css +3 -3
- package/dist/render/templates/partials/_work-timeline.liquid +1 -1
- package/dist/render/templates/project.liquid +1 -1
- package/dist/render/templates/radar/portfolio.liquid +4 -4
- package/dist/render/templates/radar/project.liquid +1 -1
- package/dist/render/templates/radar/styles.css +4 -4
- package/dist/render/templates/showcase/portfolio.liquid +4 -4
- package/dist/render/templates/showcase/project.liquid +1 -1
- package/dist/render/templates/showcase/styles.css +7 -7
- package/dist/render/templates/signal/portfolio.liquid +4 -4
- package/dist/render/templates/signal/styles.css +5 -5
- package/dist/render/templates/strata/portfolio.liquid +4 -4
- package/dist/render/templates/strata/styles.css +3 -3
- package/dist/render/templates/styles.css +39 -28
- package/dist/render/templates/terminal/portfolio.liquid +2 -2
- package/dist/render/templates/terminal/project.liquid +1 -1
- package/dist/render/templates/verdant/portfolio.liquid +4 -4
- package/dist/render/templates/verdant/styles.css +17 -2
- package/dist/render/templates/zen/portfolio.liquid +4 -4
- package/dist/render/templates/zen/styles.css +8 -8
- package/dist/routes/context.js +26 -3
- package/dist/routes/enhance.js +10 -3
- package/dist/routes/export.js +1 -0
- package/dist/routes/github.js +1 -1
- package/dist/routes/portfolio-render-data.js +15 -1
- package/dist/routes/preview.js +31 -1
- package/dist/routes/projects.js +8 -1
- package/dist/routes/publish.js +9 -2
- package/dist/settings.js +2 -0
- package/package.json +1 -1
- package/dist/public/assets/index-7mUuxgqY.js +0 -37
- package/dist/public/assets/index-CMyamplX.css +0 -1
|
@@ -30,6 +30,17 @@
|
|
|
30
30
|
--font-body: 'Inter', sans-serif;
|
|
31
31
|
--font-mono: 'IBM Plex Mono', monospace;
|
|
32
32
|
|
|
33
|
+
/* Type scale — xs is the WCAG-driven readable floor for labels/meta.
|
|
34
|
+
Templates may override individual sizes for identity, but should
|
|
35
|
+
reference these tokens before introducing new literals. */
|
|
36
|
+
--font-size-xs: 0.75rem; /* 12px — labels, meta, caption */
|
|
37
|
+
--font-size-sm: 0.875rem; /* 14px — small body, dense rows */
|
|
38
|
+
--font-size-base: 1rem; /* 16px — body */
|
|
39
|
+
--font-size-lg: 1.125rem; /* 18px — subhead */
|
|
40
|
+
--font-size-xl: 1.25rem; /* 20px — heading */
|
|
41
|
+
--font-size-2xl: 1.5rem; /* 24px — section heading */
|
|
42
|
+
--font-size-3xl: 2rem; /* 32px — page title */
|
|
43
|
+
|
|
33
44
|
--radius-sm: 0.25rem;
|
|
34
45
|
--radius-md: 0.375rem;
|
|
35
46
|
}
|
|
@@ -107,13 +118,13 @@ a:hover { text-decoration: underline; }
|
|
|
107
118
|
}
|
|
108
119
|
.section-header__title {
|
|
109
120
|
font-family: var(--font-display);
|
|
110
|
-
font-size:
|
|
121
|
+
font-size: var(--font-size-base);
|
|
111
122
|
font-weight: 600;
|
|
112
123
|
color: var(--on-surface);
|
|
113
124
|
}
|
|
114
125
|
.section-header__meta {
|
|
115
126
|
font-family: var(--font-mono);
|
|
116
|
-
font-size:
|
|
127
|
+
font-size: var(--font-size-xs);
|
|
117
128
|
text-transform: uppercase;
|
|
118
129
|
letter-spacing: 0.05em;
|
|
119
130
|
color: var(--on-surface-variant);
|
|
@@ -145,7 +156,7 @@ a:hover { text-decoration: underline; }
|
|
|
145
156
|
}
|
|
146
157
|
|
|
147
158
|
.stat-card__value--sm {
|
|
148
|
-
font-size:
|
|
159
|
+
font-size: var(--font-size-xl);
|
|
149
160
|
}
|
|
150
161
|
|
|
151
162
|
.stat-grid {
|
|
@@ -162,7 +173,7 @@ a:hover { text-decoration: underline; }
|
|
|
162
173
|
}
|
|
163
174
|
.stat-card__label {
|
|
164
175
|
font-family: var(--font-mono);
|
|
165
|
-
font-size:
|
|
176
|
+
font-size: var(--font-size-xs);
|
|
166
177
|
text-transform: uppercase;
|
|
167
178
|
letter-spacing: 0.05em;
|
|
168
179
|
color: var(--on-surface-variant);
|
|
@@ -170,7 +181,7 @@ a:hover { text-decoration: underline; }
|
|
|
170
181
|
}
|
|
171
182
|
.stat-card__value {
|
|
172
183
|
font-family: var(--font-display);
|
|
173
|
-
font-size:
|
|
184
|
+
font-size: var(--font-size-2xl);
|
|
174
185
|
font-weight: 700;
|
|
175
186
|
color: var(--on-surface);
|
|
176
187
|
}
|
|
@@ -184,7 +195,7 @@ a:hover { text-decoration: underline; }
|
|
|
184
195
|
font-family: var(--font-mono);
|
|
185
196
|
font-size: 11px;
|
|
186
197
|
line-height: 1.2;
|
|
187
|
-
padding: 0.
|
|
198
|
+
padding: 0.25rem 0.5rem;
|
|
188
199
|
border-radius: var(--radius-sm);
|
|
189
200
|
background: var(--surface-low);
|
|
190
201
|
color: var(--on-surface-variant);
|
|
@@ -210,7 +221,7 @@ a:hover { text-decoration: underline; }
|
|
|
210
221
|
|
|
211
222
|
.project-title {
|
|
212
223
|
font-family: var(--font-display);
|
|
213
|
-
font-size:
|
|
224
|
+
font-size: var(--font-size-xl);
|
|
214
225
|
font-weight: 700;
|
|
215
226
|
color: var(--on-surface);
|
|
216
227
|
margin-bottom: 0.25rem;
|
|
@@ -224,7 +235,7 @@ a:hover { text-decoration: underline; }
|
|
|
224
235
|
}
|
|
225
236
|
.project-link {
|
|
226
237
|
font-family: var(--font-mono);
|
|
227
|
-
font-size:
|
|
238
|
+
font-size: var(--font-size-xs);
|
|
228
239
|
color: var(--primary);
|
|
229
240
|
display: flex;
|
|
230
241
|
align-items: center;
|
|
@@ -284,7 +295,7 @@ a:hover { text-decoration: underline; }
|
|
|
284
295
|
text-align: left;
|
|
285
296
|
padding: 0.5rem 0;
|
|
286
297
|
font-family: var(--font-mono);
|
|
287
|
-
font-size:
|
|
298
|
+
font-size: var(--font-size-xs);
|
|
288
299
|
text-transform: uppercase;
|
|
289
300
|
letter-spacing: 0.05em;
|
|
290
301
|
color: var(--outline);
|
|
@@ -335,7 +346,7 @@ a:hover { text-decoration: underline; }
|
|
|
335
346
|
}
|
|
336
347
|
.session-card__meta {
|
|
337
348
|
color: var(--on-surface-variant);
|
|
338
|
-
font-size:
|
|
349
|
+
font-size: var(--font-size-xs);
|
|
339
350
|
}
|
|
340
351
|
.session-card__skills { margin-top: 0.5rem; }
|
|
341
352
|
|
|
@@ -348,13 +359,13 @@ a:hover { text-decoration: underline; }
|
|
|
348
359
|
}
|
|
349
360
|
.note__title {
|
|
350
361
|
font-family: var(--font-body);
|
|
351
|
-
font-size:
|
|
362
|
+
font-size: var(--font-size-sm);
|
|
352
363
|
font-weight: 600;
|
|
353
364
|
color: var(--on-surface);
|
|
354
365
|
margin-bottom: 0.25rem;
|
|
355
366
|
}
|
|
356
367
|
.note__body {
|
|
357
|
-
font-size:
|
|
368
|
+
font-size: var(--font-size-sm);
|
|
358
369
|
color: var(--on-surface-variant);
|
|
359
370
|
line-height: 1.5;
|
|
360
371
|
}
|
|
@@ -395,7 +406,7 @@ a:hover { text-decoration: underline; }
|
|
|
395
406
|
}
|
|
396
407
|
.phase-timeline__label {
|
|
397
408
|
font-family: var(--font-mono);
|
|
398
|
-
font-size:
|
|
409
|
+
font-size: var(--font-size-xs);
|
|
399
410
|
text-transform: uppercase;
|
|
400
411
|
letter-spacing: 0.05em;
|
|
401
412
|
color: var(--on-surface-variant);
|
|
@@ -410,7 +421,7 @@ a:hover { text-decoration: underline; }
|
|
|
410
421
|
}
|
|
411
422
|
.export-footer__text {
|
|
412
423
|
font-family: var(--font-mono);
|
|
413
|
-
font-size:
|
|
424
|
+
font-size: var(--font-size-xs);
|
|
414
425
|
color: var(--on-surface-variant);
|
|
415
426
|
text-transform: uppercase;
|
|
416
427
|
letter-spacing: 0.05em;
|
|
@@ -438,7 +449,7 @@ a:hover { text-decoration: underline; }
|
|
|
438
449
|
.sidebar-section { margin-bottom: 1.5rem; }
|
|
439
450
|
.sidebar-section__heading {
|
|
440
451
|
font-family: var(--font-mono);
|
|
441
|
-
font-size:
|
|
452
|
+
font-size: var(--font-size-xs);
|
|
442
453
|
text-transform: uppercase;
|
|
443
454
|
letter-spacing: 0.05em;
|
|
444
455
|
color: var(--outline);
|
|
@@ -457,7 +468,7 @@ a:hover { text-decoration: underline; }
|
|
|
457
468
|
}
|
|
458
469
|
.tool-list__name, .file-list__path {
|
|
459
470
|
font-family: var(--font-mono);
|
|
460
|
-
font-size:
|
|
471
|
+
font-size: var(--font-size-xs);
|
|
461
472
|
color: var(--on-surface);
|
|
462
473
|
overflow: hidden;
|
|
463
474
|
text-overflow: ellipsis;
|
|
@@ -465,7 +476,7 @@ a:hover { text-decoration: underline; }
|
|
|
465
476
|
}
|
|
466
477
|
.tool-list__count {
|
|
467
478
|
font-family: var(--font-mono);
|
|
468
|
-
font-size:
|
|
479
|
+
font-size: var(--font-size-xs);
|
|
469
480
|
color: var(--on-surface-variant);
|
|
470
481
|
}
|
|
471
482
|
.file-list__adds { color: var(--green); font-family: var(--font-mono); font-size: 0.6875rem; }
|
|
@@ -476,7 +487,7 @@ a:hover { text-decoration: underline; }
|
|
|
476
487
|
|
|
477
488
|
.breadcrumb {
|
|
478
489
|
font-family: var(--font-mono);
|
|
479
|
-
font-size:
|
|
490
|
+
font-size: var(--font-size-xs);
|
|
480
491
|
color: var(--on-surface-variant);
|
|
481
492
|
margin-bottom: 1rem;
|
|
482
493
|
}
|
|
@@ -486,7 +497,7 @@ a:hover { text-decoration: underline; }
|
|
|
486
497
|
.session-header { margin-bottom: 1.5rem; }
|
|
487
498
|
.session-header__title {
|
|
488
499
|
font-family: var(--font-display);
|
|
489
|
-
font-size:
|
|
500
|
+
font-size: var(--font-size-2xl);
|
|
490
501
|
font-weight: 700;
|
|
491
502
|
color: var(--on-surface);
|
|
492
503
|
margin-bottom: 0.5rem;
|
|
@@ -500,11 +511,11 @@ a:hover { text-decoration: underline; }
|
|
|
500
511
|
}
|
|
501
512
|
.session-header__date {
|
|
502
513
|
font-family: var(--font-mono);
|
|
503
|
-
font-size:
|
|
514
|
+
font-size: var(--font-size-xs);
|
|
504
515
|
}
|
|
505
516
|
.session-header__source {
|
|
506
517
|
font-family: var(--font-mono);
|
|
507
|
-
font-size:
|
|
518
|
+
font-size: var(--font-size-xs);
|
|
508
519
|
background: var(--surface-low);
|
|
509
520
|
padding: 0.125rem 0.5rem;
|
|
510
521
|
border-radius: var(--radius-sm);
|
|
@@ -521,7 +532,7 @@ a:hover { text-decoration: underline; }
|
|
|
521
532
|
.session-header__devtake p { margin: 0; }
|
|
522
533
|
.session-header__context {
|
|
523
534
|
color: var(--on-surface-variant);
|
|
524
|
-
font-size:
|
|
535
|
+
font-size: var(--font-size-sm);
|
|
525
536
|
margin: 0.75rem 0;
|
|
526
537
|
}
|
|
527
538
|
|
|
@@ -537,13 +548,13 @@ a:hover { text-decoration: underline; }
|
|
|
537
548
|
}
|
|
538
549
|
.stat__value {
|
|
539
550
|
font-family: var(--font-display);
|
|
540
|
-
font-size:
|
|
551
|
+
font-size: var(--font-size-lg);
|
|
541
552
|
font-weight: 700;
|
|
542
553
|
color: var(--on-surface);
|
|
543
554
|
}
|
|
544
555
|
.stat__label {
|
|
545
556
|
font-family: var(--font-mono);
|
|
546
|
-
font-size:
|
|
557
|
+
font-size: var(--font-size-xs);
|
|
547
558
|
text-transform: uppercase;
|
|
548
559
|
letter-spacing: 0.05em;
|
|
549
560
|
color: var(--on-surface-variant);
|
|
@@ -560,7 +571,7 @@ a:hover { text-decoration: underline; }
|
|
|
560
571
|
}
|
|
561
572
|
.content-section__heading {
|
|
562
573
|
font-family: var(--font-display);
|
|
563
|
-
font-size:
|
|
574
|
+
font-size: var(--font-size-base);
|
|
564
575
|
font-weight: 600;
|
|
565
576
|
color: var(--on-surface);
|
|
566
577
|
margin-bottom: 0.75rem;
|
|
@@ -591,7 +602,7 @@ a:hover { text-decoration: underline; }
|
|
|
591
602
|
.beat:last-child { border-bottom: none; }
|
|
592
603
|
.beat__number {
|
|
593
604
|
font-family: var(--font-mono);
|
|
594
|
-
font-size:
|
|
605
|
+
font-size: var(--font-size-xs);
|
|
595
606
|
color: var(--primary);
|
|
596
607
|
font-weight: 600;
|
|
597
608
|
min-width: 1.5rem;
|
|
@@ -601,7 +612,7 @@ a:hover { text-decoration: underline; }
|
|
|
601
612
|
.beat__content { flex: 1; min-width: 0; }
|
|
602
613
|
.beat__title {
|
|
603
614
|
font-family: var(--font-display);
|
|
604
|
-
font-size:
|
|
615
|
+
font-size: var(--font-size-sm);
|
|
605
616
|
font-weight: 600;
|
|
606
617
|
color: var(--on-surface);
|
|
607
618
|
}
|
|
@@ -627,7 +638,7 @@ a:hover { text-decoration: underline; }
|
|
|
627
638
|
}
|
|
628
639
|
.qa-pair__answer {
|
|
629
640
|
color: var(--on-surface-variant);
|
|
630
|
-
font-size:
|
|
641
|
+
font-size: var(--font-size-sm);
|
|
631
642
|
line-height: 1.5;
|
|
632
643
|
}
|
|
633
644
|
|
|
@@ -82,9 +82,9 @@
|
|
|
82
82
|
<span class="term-sep">·</span>
|
|
83
83
|
<span>{{ project.totalFilesChanged }} files</span>
|
|
84
84
|
</div>
|
|
85
|
-
{% if project.
|
|
85
|
+
{% if project.profileSkills.size > 0 %}
|
|
86
86
|
<div class="term-project-entry__skills">
|
|
87
|
-
{% for skill in project.
|
|
87
|
+
{% for skill in project.profileSkills %}
|
|
88
88
|
<span class="term-skill-tag">{{ skill }}</span>
|
|
89
89
|
{% endfor %}
|
|
90
90
|
</div>
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
{% if sessionsJson %}
|
|
70
70
|
<div class="term-section">
|
|
71
71
|
<div class="term-comment"># work timeline</div>
|
|
72
|
-
<div data-work-timeline data-sessions='{{ sessionsJson | raw }}'></div>
|
|
72
|
+
<div data-work-timeline data-sessions='{{ sessionsJson | raw }}'{% if hideSessionDates %} data-hide-dates="1"{% endif %}></div>
|
|
73
73
|
</div>
|
|
74
74
|
{% endif %}
|
|
75
75
|
|
|
@@ -142,8 +142,8 @@
|
|
|
142
142
|
<h3>{{ p.title }}</h3>
|
|
143
143
|
<span class="vd-project-card__arrow" aria-hidden="true">→</span>
|
|
144
144
|
</div>
|
|
145
|
-
{% if p.
|
|
146
|
-
<p class="vd-project-card__narrative">{{ p.
|
|
145
|
+
{% if p.tagline != blank %}
|
|
146
|
+
<p class="vd-project-card__narrative">{{ p.tagline }}</p>
|
|
147
147
|
{% endif %}
|
|
148
148
|
<div class="vd-project-card__meta">
|
|
149
149
|
<span>{{ p.totalSessions }} sessions</span>
|
|
@@ -152,9 +152,9 @@
|
|
|
152
152
|
<span>·</span>
|
|
153
153
|
<span>{{ p.totalLoc | formatLoc }} LOC</span>
|
|
154
154
|
</div>
|
|
155
|
-
{% if p.
|
|
155
|
+
{% if p.profileSkills.size > 0 %}
|
|
156
156
|
<div class="vd-skills" aria-label="Skills used">
|
|
157
|
-
{% for skill in p.
|
|
157
|
+
{% for skill in p.profileSkills %}
|
|
158
158
|
<span class="vd-skill-chip">{{ skill }}</span>
|
|
159
159
|
{% endfor %}
|
|
160
160
|
</div>
|
|
@@ -294,7 +294,7 @@
|
|
|
294
294
|
/* Project page uses smaller stat cards */
|
|
295
295
|
.verdant.heyiam-project .vd-stat-card { padding: 1rem; }
|
|
296
296
|
.verdant.heyiam-project .vd-stat-value { font-size: 1.5rem; }
|
|
297
|
-
.verdant.heyiam-project .vd-stat-label { font-size: 0.
|
|
297
|
+
.verdant.heyiam-project .vd-stat-label { font-size: 0.75rem; }
|
|
298
298
|
|
|
299
299
|
/* ── Section Headers ── */
|
|
300
300
|
.verdant .vd-section-header {
|
|
@@ -482,7 +482,7 @@
|
|
|
482
482
|
|
|
483
483
|
/* ── Narrative ── */
|
|
484
484
|
.verdant .vd-narrative {
|
|
485
|
-
font-size:
|
|
485
|
+
font-size: 1rem;
|
|
486
486
|
line-height: 1.8;
|
|
487
487
|
color: var(--vd-on-surface-2);
|
|
488
488
|
}
|
|
@@ -1256,6 +1256,21 @@
|
|
|
1256
1256
|
.verdant .vd-footer { flex-direction: column; gap: 0.5rem; text-align: center; }
|
|
1257
1257
|
}
|
|
1258
1258
|
|
|
1259
|
+
@media (max-width: 480px) {
|
|
1260
|
+
.verdant .vd-page { padding: 1rem 0.75rem 2rem; }
|
|
1261
|
+
.verdant .vd-hero h1 { font-size: 1.375rem; }
|
|
1262
|
+
.verdant .vd-hero__avatar { width: 64px; height: 64px; font-size: 1.375rem; }
|
|
1263
|
+
.verdant .vd-hero__photo { width: 80px; height: 104px; }
|
|
1264
|
+
.verdant .vd-stats,
|
|
1265
|
+
.verdant .vd-stat-grid { grid-template-columns: 1fr; }
|
|
1266
|
+
.verdant .vd-sidebar { grid-template-columns: 1fr; }
|
|
1267
|
+
.verdant .vd-project-header h1 { font-size: 1.375rem; }
|
|
1268
|
+
.verdant .vd-session-header h1 { font-size: 1.25rem; }
|
|
1269
|
+
.verdant .vd-vine-timeline,
|
|
1270
|
+
.verdant .vd-beats { padding-left: 1.25rem; }
|
|
1271
|
+
.verdant .vd-nav__links { flex-wrap: wrap; gap: 0.75rem; }
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1259
1274
|
|
|
1260
1275
|
/* Live-edit empty field hiding */
|
|
1261
1276
|
[data-portfolio-empty="true"] { display: none; }
|
|
@@ -71,15 +71,15 @@
|
|
|
71
71
|
<h3 class="zen-project-title">
|
|
72
72
|
<a href="/{{ user.username }}/{{ p.slug }}">{{ p.title }}</a>
|
|
73
73
|
</h3>
|
|
74
|
-
{% if p.
|
|
75
|
-
<p class="zen-project-narrative">{{ p.
|
|
74
|
+
{% if p.tagline != blank %}
|
|
75
|
+
<p class="zen-project-narrative">{{ p.tagline }}</p>
|
|
76
76
|
{% endif %}
|
|
77
77
|
<p class="zen-project-meta">
|
|
78
78
|
{{ p.totalSessions }} sessions · {{ p.totalDurationMinutes | formatDuration }} · {{ p.totalLoc | localeNumber }} lines changed
|
|
79
79
|
</p>
|
|
80
|
-
{% if p.
|
|
80
|
+
{% if p.profileSkills.size > 0 %}
|
|
81
81
|
<p class="zen-project-skills">
|
|
82
|
-
{{ p.
|
|
82
|
+
{{ p.profileSkills | join: ", " }}
|
|
83
83
|
</p>
|
|
84
84
|
{% endif %}
|
|
85
85
|
{% if p.sourceCounts.size > 0 %}
|
|
@@ -276,7 +276,7 @@ a:active {
|
|
|
276
276
|
|
|
277
277
|
.zen-body {
|
|
278
278
|
font-family: var(--zen-font-body);
|
|
279
|
-
font-size:
|
|
279
|
+
font-size: 1rem;
|
|
280
280
|
line-height: 1.8;
|
|
281
281
|
color: var(--zen-text-secondary);
|
|
282
282
|
}
|
|
@@ -342,7 +342,7 @@ a:active {
|
|
|
342
342
|
|
|
343
343
|
.zen-header-bio {
|
|
344
344
|
font-family: var(--zen-font-body);
|
|
345
|
-
font-size:
|
|
345
|
+
font-size: 1rem;
|
|
346
346
|
line-height: 1.8;
|
|
347
347
|
color: var(--zen-text-secondary);
|
|
348
348
|
margin-block-end: 1rem;
|
|
@@ -500,7 +500,7 @@ a:active {
|
|
|
500
500
|
|
|
501
501
|
.zen-project-narrative {
|
|
502
502
|
font-family: var(--zen-font-body);
|
|
503
|
-
font-size:
|
|
503
|
+
font-size: 1rem;
|
|
504
504
|
line-height: 1.8;
|
|
505
505
|
color: var(--zen-text-secondary);
|
|
506
506
|
margin-block-end: 1.25rem;
|
|
@@ -591,7 +591,7 @@ a:active {
|
|
|
591
591
|
/* ── Narrative ── */
|
|
592
592
|
.zen-narrative p {
|
|
593
593
|
font-family: var(--zen-font-body);
|
|
594
|
-
font-size:
|
|
594
|
+
font-size: 1rem;
|
|
595
595
|
line-height: 1.9;
|
|
596
596
|
color: var(--zen-text-secondary);
|
|
597
597
|
margin-block-end: 1.5rem;
|
|
@@ -703,7 +703,7 @@ a:active {
|
|
|
703
703
|
|
|
704
704
|
.zen-decision-text {
|
|
705
705
|
font-family: var(--zen-font-body);
|
|
706
|
-
font-size:
|
|
706
|
+
font-size: 1rem;
|
|
707
707
|
line-height: 1.7;
|
|
708
708
|
color: var(--zen-text-secondary);
|
|
709
709
|
}
|
|
@@ -837,7 +837,7 @@ a:active {
|
|
|
837
837
|
|
|
838
838
|
.zen-beat-desc {
|
|
839
839
|
font-family: var(--zen-font-body);
|
|
840
|
-
font-size:
|
|
840
|
+
font-size: 1rem;
|
|
841
841
|
line-height: 1.85;
|
|
842
842
|
color: var(--zen-text-secondary);
|
|
843
843
|
}
|
|
@@ -861,7 +861,7 @@ a:active {
|
|
|
861
861
|
|
|
862
862
|
.zen-question {
|
|
863
863
|
font-family: var(--zen-font-body);
|
|
864
|
-
font-size:
|
|
864
|
+
font-size: 1rem;
|
|
865
865
|
font-weight: 500;
|
|
866
866
|
line-height: 1.7;
|
|
867
867
|
color: var(--zen-text);
|
|
@@ -870,7 +870,7 @@ a:active {
|
|
|
870
870
|
|
|
871
871
|
.zen-answer {
|
|
872
872
|
font-family: var(--zen-font-body);
|
|
873
|
-
font-size:
|
|
873
|
+
font-size: 1rem;
|
|
874
874
|
line-height: 1.85;
|
|
875
875
|
color: var(--zen-text-secondary);
|
|
876
876
|
}
|
package/dist/routes/context.js
CHANGED
|
@@ -9,8 +9,8 @@ import { bridgeToAnalyzer, mergeActiveIntervals, sumIntervalMs } from '../bridge
|
|
|
9
9
|
import { analyzeSession } from '../analyzer.js';
|
|
10
10
|
import { loadEnhancedData, loadProjectEnhanceResult, getUploadedState, } from '../settings.js';
|
|
11
11
|
import { getTemplateCss } from '../render/templates.js';
|
|
12
|
-
import { archiveSessionFiles } from '../archive.js';
|
|
13
|
-
import { getDatabase, openDatabase, getSessionStats as dbGetSessionStats, getSessionCount, getAllSessionMetas, getAllProjectStats, getSessionsByProject, getProjectUuid, } from '../db.js';
|
|
12
|
+
import { archiveSessionFiles, findReadableSessionPath } from '../archive.js';
|
|
13
|
+
import { getDatabase, openDatabase, getSessionStats as dbGetSessionStats, getSessionCount, getAllSessionMetas, getAllProjectStats, getSessionsByProject, getProjectUuid, getSessionRow, updateSessionPath, } from '../db.js';
|
|
14
14
|
import { ensureSessionIndexed, displayNameFromDir } from '../sync.js';
|
|
15
15
|
export { displayNameFromDir };
|
|
16
16
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -298,11 +298,34 @@ export function createRouteContext(sessionsBasePath, dbPath) {
|
|
|
298
298
|
};
|
|
299
299
|
}
|
|
300
300
|
async function loadSession(sessionPath, projectName, sessionId) {
|
|
301
|
-
const
|
|
301
|
+
const readablePath = await resolveSessionReadPath(sessionPath, sessionId);
|
|
302
|
+
const parsed = await parseSession(readablePath);
|
|
302
303
|
const analyzerInput = bridgeToAnalyzer(parsed, { sessionId, projectName });
|
|
303
304
|
const session = analyzeSession(analyzerInput);
|
|
304
305
|
return mergeEnhancedData(session);
|
|
305
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Returns a path to a readable session file. If the originally-indexed
|
|
309
|
+
* path still exists, returns it unchanged. Otherwise falls back to the
|
|
310
|
+
* archive copy and heals the DB cache so future reads skip the lookup.
|
|
311
|
+
*
|
|
312
|
+
* This compensates for source tools (notably Claude Code's 30-day
|
|
313
|
+
* cleanup) deleting session files after we've indexed them.
|
|
314
|
+
*/
|
|
315
|
+
async function resolveSessionReadPath(originalPath, sessionId) {
|
|
316
|
+
const row = getSessionRow(db, sessionId);
|
|
317
|
+
const projectDir = row?.project_dir;
|
|
318
|
+
if (!projectDir)
|
|
319
|
+
return originalPath;
|
|
320
|
+
const readable = await findReadableSessionPath(originalPath, projectDir);
|
|
321
|
+
if (!readable)
|
|
322
|
+
return originalPath;
|
|
323
|
+
if (readable !== originalPath) {
|
|
324
|
+
updateSessionPath(db, sessionId, readable);
|
|
325
|
+
console.log(`[loadSession] Healed stale path for ${sessionId} → archive copy`);
|
|
326
|
+
}
|
|
327
|
+
return readable;
|
|
328
|
+
}
|
|
306
329
|
// ── getSessionStats ──────────────────────────────────────
|
|
307
330
|
async function getSessionStats(meta, projectName) {
|
|
308
331
|
try {
|
package/dist/routes/enhance.js
CHANGED
|
@@ -5,7 +5,7 @@ import { enhanceProject, refineNarrative } from '../llm/project-enhance.js';
|
|
|
5
5
|
import { getAnthropicApiKey, saveEnhancedData, loadEnhancedData, deleteEnhancedData, loadFreshProjectEnhanceResult, saveProjectEnhanceResult, loadProjectEnhanceResult, buildProjectFingerprint, getUploadedState, } from '../settings.js';
|
|
6
6
|
import { requireProject } from './context.js';
|
|
7
7
|
import { startSSE } from './sse.js';
|
|
8
|
-
import { invalidatePortfolioPreviewCache } from './preview.js';
|
|
8
|
+
import { invalidatePortfolioPreviewCache, invalidateProjectPreviewCache } from './preview.js';
|
|
9
9
|
export function createEnhanceRouter(ctx) {
|
|
10
10
|
const router = Router();
|
|
11
11
|
// Triage endpoint -- AI selects which sessions are worth showcasing (SSE stream)
|
|
@@ -259,7 +259,11 @@ export function createEnhanceRouter(ctx) {
|
|
|
259
259
|
// Save project enhance result explicitly
|
|
260
260
|
router.post('/api/projects/:project/enhance-save', async (req, res) => {
|
|
261
261
|
const project = String(req.params.project);
|
|
262
|
-
|
|
262
|
+
// FIXME(security): screenshotBase64 is accepted with no size cap and no
|
|
263
|
+
// `data:image/(png|jpeg|jpg|webp);base64,...` shape check. Local-only CLI
|
|
264
|
+
// so not a privilege issue today, but worth a regex + ~4 MB cap for
|
|
265
|
+
// defense-in-depth and to keep the cache JSON from ballooning.
|
|
266
|
+
const { selectedSessionIds, result, title, repoUrl, projectUrl, screenshotBase64, hideSessionDates } = req.body;
|
|
263
267
|
if (!Array.isArray(selectedSessionIds) || !result?.narrative) {
|
|
264
268
|
res.status(400).json({ error: { code: 'INVALID_INPUT', message: 'selectedSessionIds and result are required' } });
|
|
265
269
|
return;
|
|
@@ -272,9 +276,12 @@ export function createEnhanceRouter(ctx) {
|
|
|
272
276
|
const proj = await requireProject(ctx, project, res);
|
|
273
277
|
if (!proj)
|
|
274
278
|
return;
|
|
275
|
-
saveProjectEnhanceResult(proj.dirName, selectedSessionIds, result, undefined, { title, repoUrl, projectUrl, screenshotBase64 });
|
|
279
|
+
saveProjectEnhanceResult(proj.dirName, selectedSessionIds, result, undefined, { title, repoUrl, projectUrl, screenshotBase64, hideSessionDates });
|
|
276
280
|
// Project title/narrative/skills appear in portfolio listing — bust cache.
|
|
277
281
|
invalidatePortfolioPreviewCache();
|
|
282
|
+
// Per-project render cache is keyed by the URL param the client passed.
|
|
283
|
+
invalidateProjectPreviewCache(project);
|
|
284
|
+
invalidateProjectPreviewCache(proj.dirName);
|
|
278
285
|
res.json({ saved: true, enhancedAt: new Date().toISOString() });
|
|
279
286
|
}
|
|
280
287
|
catch (err) {
|
package/dist/routes/export.js
CHANGED
package/dist/routes/github.js
CHANGED
|
@@ -188,7 +188,7 @@ export function createGithubRouter(ctx) {
|
|
|
188
188
|
fingerprint: 'gh-publish',
|
|
189
189
|
enhancedAt: new Date().toISOString(),
|
|
190
190
|
selectedSessionIds: detail.sessions.map((s) => s.id),
|
|
191
|
-
result: { narrative: '', arc: [], skills: [], timeline: [], questions: [] },
|
|
191
|
+
result: { tagline: '', narrative: '', arc: [], skills: [], timeline: [], questions: [] },
|
|
192
192
|
};
|
|
193
193
|
const proj = detail.project;
|
|
194
194
|
projectInputs.push({
|
|
@@ -2,6 +2,7 @@ import { getPortfolioProfile, loadProjectEnhanceResult } from '../settings.js';
|
|
|
2
2
|
import { getSessionsByProject, getAllProjectStats } from '../db.js';
|
|
3
3
|
import { displayNameFromDir } from '../sync.js';
|
|
4
4
|
import { toSlug } from '../format-utils.js';
|
|
5
|
+
import { selectProfileSkills } from '../render/select-profile-skills.js';
|
|
5
6
|
/**
|
|
6
7
|
* Assemble the `PortfolioRenderData` payload from local project data.
|
|
7
8
|
*
|
|
@@ -54,16 +55,29 @@ export async function buildPortfolioRenderData(ctx, auth, opts = {}) {
|
|
|
54
55
|
loc: (s.loc_added || 0) + (s.loc_removed || 0),
|
|
55
56
|
durationMinutes: s.duration_minutes || 0,
|
|
56
57
|
}));
|
|
58
|
+
const projectSkills = cached?.result?.skills || proj.skills || [];
|
|
59
|
+
const sessionSkills = dbSessions
|
|
60
|
+
.filter((s) => !s.is_subagent && s.skills)
|
|
61
|
+
.map((s) => {
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(s.skills);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
});
|
|
57
69
|
portfolioProjects.push({
|
|
58
70
|
slug: toSlug(title),
|
|
59
71
|
title,
|
|
72
|
+
tagline: cached?.result?.tagline || '',
|
|
60
73
|
narrative: cached?.result?.narrative || proj.description || '',
|
|
61
74
|
totalSessions: projSessions,
|
|
62
75
|
totalLoc: projLoc,
|
|
63
76
|
totalDurationMinutes: projDuration,
|
|
64
77
|
totalAgentDurationMinutes: projAgentDuration,
|
|
65
78
|
totalFilesChanged: proj.totalFiles || 0,
|
|
66
|
-
skills:
|
|
79
|
+
skills: projectSkills,
|
|
80
|
+
profileSkills: selectProfileSkills({ projectSkills, sessionSkills }),
|
|
67
81
|
publishedCount: 0,
|
|
68
82
|
sessions: sessionActivity,
|
|
69
83
|
});
|
package/dist/routes/preview.js
CHANGED
|
@@ -14,6 +14,7 @@ import { displayNameFromDir } from '../sync.js';
|
|
|
14
14
|
import { toSlug } from '../format-utils.js';
|
|
15
15
|
import { getSessionsByProject, getAllProjectStats } from '../db.js';
|
|
16
16
|
import { applyPortfolioProjectFilter } from './portfolio-render-data.js';
|
|
17
|
+
import { selectProfileSkills } from '../render/select-profile-skills.js';
|
|
17
18
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
19
|
/**
|
|
19
20
|
* In-memory cache for expensive buildProjectPreviewData calls.
|
|
@@ -53,6 +54,15 @@ function portfolioCacheKey(templateName) {
|
|
|
53
54
|
export function invalidatePortfolioPreviewCache() {
|
|
54
55
|
portfolioPreviewCache.clear();
|
|
55
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Drop the per-project render-data cache for `projectParam`. Call after any
|
|
59
|
+
* mutation that affects the rendered project page (title, URLs, narrative,
|
|
60
|
+
* screenshot, skills, timeline). Without this, the 30s TTL on
|
|
61
|
+
* previewDataCache masks the change in the React UI.
|
|
62
|
+
*/
|
|
63
|
+
export function invalidateProjectPreviewCache(projectParam) {
|
|
64
|
+
previewDataCache.delete(projectParam);
|
|
65
|
+
}
|
|
56
66
|
/** Test helper: read current cache entry without mutating. */
|
|
57
67
|
export function _getPortfolioPreviewCacheEntry(templateName = 'editorial') {
|
|
58
68
|
return portfolioPreviewCache.get(portfolioCacheKey(templateName));
|
|
@@ -176,6 +186,12 @@ async function buildProjectPreviewData(ctx, projectParam, queryOverrides) {
|
|
|
176
186
|
repoUrl: metaRepoUrl,
|
|
177
187
|
projectUrl: metaProjectUrl,
|
|
178
188
|
screenshotUrl: (() => {
|
|
189
|
+
// Manual uploads land only in the enhance-cache JSON (no disk write),
|
|
190
|
+
// so check the cache first — mirrors export.ts:resolveScreenshotDataUri.
|
|
191
|
+
const b64 = cachedAny?.screenshotBase64;
|
|
192
|
+
if (b64) {
|
|
193
|
+
return b64.startsWith('data:') ? b64 : `data:image/png;base64,${b64}`;
|
|
194
|
+
}
|
|
179
195
|
return existsSync(path.join(SCREENSHOTS_DIR, `${slug}.png`))
|
|
180
196
|
? `/screenshots/${slug}.png`
|
|
181
197
|
: undefined;
|
|
@@ -192,6 +208,7 @@ async function buildProjectPreviewData(ctx, projectParam, queryOverrides) {
|
|
|
192
208
|
allSessionCards,
|
|
193
209
|
sessionBaseUrl: `/preview/project/${encodeURIComponent(projectParam)}/session`,
|
|
194
210
|
sessionSuffix: '.html',
|
|
211
|
+
hideSessionDates: cachedAny?.hideSessionDates,
|
|
195
212
|
});
|
|
196
213
|
const result = { renderData, enhanceResult, projName: projAny.name };
|
|
197
214
|
// Cache the result (template-agnostic data, re-rendered cheaply per template)
|
|
@@ -587,16 +604,29 @@ body { overflow: auto !important; min-height: auto !important; }
|
|
|
587
604
|
loc: (s.loc_added || 0) + (s.loc_removed || 0),
|
|
588
605
|
durationMinutes: s.duration_minutes || 0,
|
|
589
606
|
}));
|
|
607
|
+
const projectSkills = cached?.result?.skills || proj.skills || [];
|
|
608
|
+
const sessionSkills = dbSessions
|
|
609
|
+
.filter(s => !s.is_subagent && s.skills)
|
|
610
|
+
.map(s => {
|
|
611
|
+
try {
|
|
612
|
+
return JSON.parse(s.skills);
|
|
613
|
+
}
|
|
614
|
+
catch {
|
|
615
|
+
return [];
|
|
616
|
+
}
|
|
617
|
+
});
|
|
590
618
|
portfolioProjects.push({
|
|
591
619
|
slug: toSlug(title),
|
|
592
620
|
title,
|
|
621
|
+
tagline: cached?.result?.tagline || '',
|
|
593
622
|
narrative: cached?.result?.narrative || proj.description || '',
|
|
594
623
|
totalSessions: projSessions,
|
|
595
624
|
totalLoc: projLoc,
|
|
596
625
|
totalDurationMinutes: projDuration,
|
|
597
626
|
totalAgentDurationMinutes: projAgentDuration,
|
|
598
627
|
totalFilesChanged: proj.totalFiles || 0,
|
|
599
|
-
skills:
|
|
628
|
+
skills: projectSkills,
|
|
629
|
+
profileSkills: selectProfileSkills({ projectSkills, sessionSkills }),
|
|
600
630
|
publishedCount: 0,
|
|
601
631
|
sessions: sessionActivity,
|
|
602
632
|
});
|
package/dist/routes/projects.js
CHANGED
|
@@ -217,7 +217,14 @@ export function createProjectsRouter(ctx) {
|
|
|
217
217
|
res.status(400).json({ error: { code: 'NO_CACHE', message: 'Project must be enhanced before managing sessions' } });
|
|
218
218
|
return;
|
|
219
219
|
}
|
|
220
|
-
saveProjectEnhanceResult(proj.dirName, selectedSessionIds, cache.result, undefined, {
|
|
220
|
+
saveProjectEnhanceResult(proj.dirName, selectedSessionIds, cache.result, undefined, {
|
|
221
|
+
title: cache.title,
|
|
222
|
+
repoUrl: cache.repoUrl,
|
|
223
|
+
projectUrl: cache.projectUrl,
|
|
224
|
+
screenshotBase64: cache.screenshotBase64,
|
|
225
|
+
template: cache.template,
|
|
226
|
+
hideSessionDates: cache.hideSessionDates,
|
|
227
|
+
});
|
|
221
228
|
invalidatePortfolioPreviewCache();
|
|
222
229
|
res.json({ ok: true, selectedSessionIds });
|
|
223
230
|
}
|