@@ -416,6 +416,16 @@ document.getElementById('back-btn').addEventListener('click', () => {
416416// ── Microblog ────────────────────────────────────────────────────────────────
417417/* eslint-disable quotes */
418418const microblogEntries = [
419+ {
420+ id : "(Git) worktrees-vs-branches" ,
421+ date : "Mar. '26" ,
422+ title : "(Git) Worktrees vs Branches" ,
423+ tags : [ "git" , "workflow" ] ,
424+ diagram : "worktree" ,
425+ content : `<p>A <strong>branch</strong> is just a pointer to a commit. Switching branches swaps files in your single working directory. Which forces one on stashing uncommitted work. A <strong>worktree</strong> (<code>git worktree add</code>) gives each branch its own directory, all backed by the same <code>.git</code> object store. Thus, no stashing required, a personal ick only tbh.</p>
426+ <p>The agentic advantage: worktrees let you run coding agents in parallel. Each agent gets its own isolated checkout while sharing the same repo. <a href="https://github.com/jj-vcs/jj" target="_blank">Jujutsu (jj)</a> is an open-source alternative that bakes this kind of parallel workflow into the VCS itself. Find a better deep dive into other alternatives <a href="https://blog.ezyang.com/2026/03/parallel-agents-heart-sapling/" target="_blank">here</a>.</p>` ,
427+ links : [ ]
428+ } ,
419429 {
420430 id : "deformable-convolutions" ,
421431 date : "Mar. '26" ,
@@ -434,8 +444,146 @@ const microblogList = document.getElementById('microblog-list');
434444const microblogDetail = document . getElementById ( 'microblog-detail' ) ;
435445let microblogTagSwitching = false ;
436446
447+ // ── Worktree vs Branches Diagram ─────────────────────────────────────────────
448+ function buildWorktreeDiagram ( ) {
449+ const container = document . getElementById ( 'microblog-diagram-worktree' ) ;
450+ if ( ! container ) return ;
451+
452+ let mode = 'branch' ;
453+
454+ // Shared geometry
455+ const W = 300 , H = 150 ;
456+ const gitX = W / 2 , gitY = 22 , gitRx = 30 , gitRy = 12 ;
457+
458+ // Animated node positions: [gitEllipse, ...boxes, ...branch-labels]
459+ // We'll track box centres and animate between layouts
460+ const branchLayout = {
461+ boxes : [ { x : W / 2 , y : 90 } ] ,
462+ labels : [
463+ { x : 46 , y : 90 , text : 'main' , dim : true } ,
464+ { x : W / 2 , y : 130 , text : 'feat-A' , dim : false } ,
465+ { x : 254 , y : 90 , text : 'fix-B' , dim : true }
466+ ] ,
467+ connectors : [ [ gitX , gitY + gitRy , W / 2 , 90 - 15 ] ] ,
468+ footnote : 'stash \u2192 checkout \u2192 switch'
469+ } ;
470+
471+ const worktreeLayout = {
472+ boxes : [
473+ { x : W / 2 - 65 , y : 90 } ,
474+ { x : W / 2 + 65 , y : 90 }
475+ ] ,
476+ labels : [ ] ,
477+ connectors : [
478+ [ gitX - 10 , gitY + gitRy , W / 2 - 65 , 90 - 15 ] ,
479+ [ gitX + 10 , gitY + gitRy , W / 2 + 65 , 90 - 15 ]
480+ ] ,
481+ footnote : 'parallel \u2014 no stashing needed'
482+ } ;
483+
484+ function buildSvg ( layout ) {
485+ const bw = 90 , bh = 30 ;
486+ let s = `<svg viewBox="0 0 ${ W } ${ H } " class="wt-svg" xmlns="http://www.w3.org/2000/svg">` ;
487+
488+ // .git ellipse
489+ s += `<ellipse cx="${ gitX } " cy="${ gitY } " rx="${ gitRx } " ry="${ gitRy } "
490+ fill="none" stroke="var(--text-secondary)" stroke-width="1" stroke-dasharray="3 2" opacity="0.55"/>` ;
491+ s += `<text x="${ gitX } " y="${ gitY + 3.5 } " text-anchor="middle"
492+ font-family="var(--font-mono)" font-size="8" fill="var(--text-secondary)">.git</text>` ;
493+
494+ // Connectors (dashed lines from .git to boxes)
495+ layout . connectors . forEach ( ( [ x1 , y1 , x2 , y2 ] ) => {
496+ s += `<line x1="${ x1 } " y1="${ y1 } " x2="${ x2 } " y2="${ y2 } "
497+ stroke="var(--text-secondary)" stroke-width="0.7" stroke-dasharray="3 2" opacity="0.45"/>` ;
498+ } ) ;
499+
500+ // Boxes
501+ layout . boxes . forEach ( ( b , i ) => {
502+ const col = layout . boxes . length > 1
503+ ? ( i === 0 ? 'var(--link)' : 'var(--text)' )
504+ : 'var(--text)' ;
505+ s += `<rect x="${ b . x - bw / 2 } " y="${ b . y - bh / 2 } " width="${ bw } " height="${ bh } "
506+ rx="3" fill="none" stroke="${ col } " stroke-width="1"/>` ;
507+ // Inner label for worktree mode, "working dir" for branch mode
508+ const inner = layout . boxes . length === 1 ? 'working dir' : ( i === 0 ? 'feat-A/' : 'fix-B/' ) ;
509+ s += `<text x="${ b . x } " y="${ b . y + 3 } " text-anchor="middle"
510+ font-family="var(--font-mono)" font-size="7.5" fill="${ col } ">${ inner } </text>` ;
511+ } ) ;
512+
513+ // Branch labels (only in branch mode)
514+ layout . labels . forEach ( l => {
515+ const col = l . dim ? 'var(--text-secondary)' : 'var(--link)' ;
516+ const op = l . dim ? '0.4' : '1' ;
517+ s += `<text x="${ l . x } " y="${ l . y + 3 } " text-anchor="middle"
518+ font-family="var(--font-mono)" font-size="7" fill="${ col } " opacity="${ op } ">${ l . text } </text>` ;
519+ // Active indicator arrow for the checked-out branch
520+ if ( ! l . dim && layout . boxes . length === 1 ) {
521+ const bx = layout . boxes [ 0 ] . x ;
522+ s += `<line x1="${ l . x } " y1="${ l . y - 4 } " x2="${ bx } " y2="${ layout . boxes [ 0 ] . y + bh / 2 } "
523+ stroke="var(--link)" stroke-width="0.6" stroke-dasharray="2 2" opacity="0.45"/>` ;
524+ }
525+ } ) ;
526+
527+ // Footnote
528+ s += `<text x="${ W / 2 } " y="${ H - 4 } " text-anchor="middle"
529+ font-family="var(--font-mono)" font-size="6.5" fill="var(--text-secondary)" opacity="0.5">${ layout . footnote } </text>` ;
530+
531+ s += '</svg>' ;
532+ return s ;
533+ }
534+
535+ function render ( ) {
536+ const layout = mode === 'branch' ? branchLayout : worktreeLayout ;
537+ container . querySelector ( '.wt-diagram-area' ) . innerHTML = buildSvg ( layout ) ;
538+ }
539+
540+ // Toggle buttons (reuse dcn-toggle / dcn-btn styles)
541+ const toggle = document . createElement ( 'div' ) ;
542+ toggle . className = 'dcn-toggle' ;
543+
544+ const btnBr = document . createElement ( 'button' ) ;
545+ btnBr . className = 'dcn-btn active' ;
546+ btnBr . textContent = 'Branches' ;
547+
548+ const btnWt = document . createElement ( 'button' ) ;
549+ btnWt . className = 'dcn-btn' ;
550+ btnWt . textContent = 'Worktrees' ;
551+
552+ function setMode ( m ) {
553+ if ( m === mode ) return ;
554+ mode = m ;
555+ btnBr . classList . toggle ( 'active' , mode === 'branch' ) ;
556+ btnWt . classList . toggle ( 'active' , mode === 'worktree' ) ;
557+ const area = container . querySelector ( '.wt-diagram-area' ) ;
558+ area . classList . add ( 'wt-fading' ) ;
559+ setTimeout ( ( ) => {
560+ render ( ) ;
561+ area . classList . remove ( 'wt-fading' ) ;
562+ } , 200 ) ;
563+ }
564+
565+ btnBr . addEventListener ( 'click' , ( ) => setMode ( 'branch' ) ) ;
566+ btnWt . addEventListener ( 'click' , ( ) => setMode ( 'worktree' ) ) ;
567+
568+ toggle . appendChild ( btnBr ) ;
569+ toggle . appendChild ( btnWt ) ;
570+
571+ const area = document . createElement ( 'div' ) ;
572+ area . className = 'wt-diagram-area' ;
573+
574+ const caption = document . createElement ( 'p' ) ;
575+ caption . className = 'dcn-caption' ;
576+ caption . textContent = 'Click to compare single-directory branching with parallel worktrees.' ;
577+
578+ container . appendChild ( toggle ) ;
579+ container . appendChild ( area ) ;
580+ container . appendChild ( caption ) ;
581+ render ( ) ;
582+ }
583+
437584// ── Deformable Convolution Diagram ───────────────────────────────────────────
438585function buildMicroblogDiagram ( type ) {
586+ if ( type === 'worktree' ) { buildWorktreeDiagram ( ) ; return ; }
439587 if ( type !== 'deformable-conv' ) return ;
440588 const container = document . getElementById ( 'microblog-diagram-deformable-conv' ) ;
441589 if ( ! container ) return ;
0 commit comments