{"avgDl":545.6,"df":{"0":4,"00":1,"000":2,"0010":1,"03":1,"1":22,"10":5,"100":1,"12":7,"150":1,"1516":1,"152":2,"1565":1,"1640s":1,"16th":1,"17":1,"1767":1,"1927":1,"1945":1,"1952":1,"1953":1,"1960s":1,"1963":1,"1980s":1,"1981":1,"1995":1,"1998":1,"2":22,"20":4,"200":1,"2001":1,"2017":1,"2026":5,"2040":1,"20is":1,"20zetl":1,"256":1,"27":2,"280":1,"299":1,"2fa":1,"3":19,"30":4,"300":1,"3000":4,"3339":1,"397":1,"3rd":1,"4":12,"40":3,"400":1,"404":1,"48":1,"5":10,"50":4,"5000":2,"550":1,"58":1,"6":2,"60":3,"64":3,"7":3,"72":1,"72h":1,"80":4,"8601":3,"9":3,"90":3,"a":55,"abandoned":1,"abduction":5,"abductive":1,"abort":1,"aborts":1,"about":30,"above":6,"absence":2,"absent":2,"absolute":3,"abstract":1,"academic":2,"accept":3,"accepted":1,"accepting":1,"accepts":2,"access":16,"accessibility":1,"accessible":2,"accidentally":1,"accidents":1,"accordingly":1,"account":4,"accounts":8,"accrete":1,"accumulate":2,"accumulated":1,"acme":1,"across":13,"act":2,"acting":1,"action":2,"active":7,"actively":3,"activity":1,"acts":2,"actual":1,"actually":15,"adapter":1,"adapters":4,"add":13,"added":6,"adding":2,"additional":1,"additive":1,"address":2,"addressable":3,"addressed":1,"addresses":1,"addressing":1,"adds":3,"adjust":1,"admin":3,"admins":2,"adopting":1,"advice":3,"affects":1,"affordance":2,"after":16,"again":2,"against":14,"agent":10,"agents":8,"ago":3,"agpl":2,"ahead":2,"ahrens":1,"ai":7,"aim":1,"aimed":1,"airplane":1,"aliased":3,"aliases":3,"alice":3,"all":29,"allow":1,"allowed":1,"almost":7,"alone":4,"along":4,"alongside":11,"aloud":1,"alphabetical":1,"alphabetised":1,"already":12,"also":12,"alternate":2,"alternative":2,"always":11,"am":1,"ambiguity":1,"ambiguous":2,"an":42,"analogue":2,"analysis":2,"analytics":1,"ancestor":1,"anchor":3,"anchors":5,"and":55,"annotate":1,"annotated":1,"annoying":1,"another":11,"ansi":2,"answer":12,"answers":7,"anti":1,"anuna":10,"any":37,"anyone":3,"anything":13,"anyway":2,"anywhere":6,"apart":1,"api":6,"app":1,"appear":5,"appeared":2,"appears":5,"append":1,"appended":1,"application":1,"applies":2,"apply":1,"approaches":1,"appropriate":1,"approval":1,"approve":1,"approved":1,"approves":1,"arbitrary":1,"arc":1,"arca":1,"architectural":3,"architecture":4,"archival":1,"are":48,"area":2,"aren":3,"arent":1,"argument":3,"arithmetic":2,"ark":1,"around":11,"array":2,"arrive":1,"arrives":3,"arriving":1,"arrows":1,"articles":1,"artificiality":1,"as":45,"ask":11,"asked":2,"asking":1,"asks":2,"aspirational":1,"assembled":2,"assert":1,"asserted":1,"asserting":2,"asserts":1,"asset":2,"assets":3,"assigned":1,"assigns":1,"association":1,"associative":1,"assume":1,"assumes":1,"assumptions":1,"ast":8,"asymmetry":1,"at":52,"atlantic":1,"atomic":2,"attach":1,"attached":1,"attempting":1,"attribute":1,"attribution":2,"audit":8,"auditing":2,"audits":1,"augments":1,"auth":4,"authentic":1,"authenticated":1,"authentication":1,"authenticator":2,"author":10,"authoring":4,"authoritative":3,"authority":1,"authors":3,"authorship":1,"auto":11,"autodesk":1,"automatic":2,"automatically":2,"automation":4,"autosave":1,"available":15,"avoiding":1,"aware":5,"awareness":1,"away":4,"awkwardly":1,"b":2,"b2":1,"back":25,"backblaze":1,"backed":4,"background":1,"backing":2,"backlink":8,"backlinks":23,"backs":1,"backstop":1,"backward":1,"backwards":1,"bad":1,"band":1,"banner":2,"bare":2,"bargain":1,"base":3,"based":6,"basics":1,"batch":1,"be":18,"bearer":1,"bearing":1,"beautifully":1,"because":8,"become":6,"becomes":7,"becoming":1,"been":6,"before":14,"behaviour":4,"behavioural":2,"behind":6,"being":3,"belief":1,"beliefs":1,"belong":1,"belongs":1,"below":5,"benchmark":1,"benefit":1,"beside":1,"best":2,"better":5,"between":18,"beyond":2,"bfs":1,"bibliographies":1,"bidirectional":1,"big":3,"bigger":3,"binaries":1,"binary":12,"bind":2,"binds":5,"bip39":6,"bird":1,"birds":1,"bit":2,"bites":2,"bits":1,"blake3":5,"blip":1,"blob":2,"block":24,"blocked":4,"blockers":1,"blocking":1,"blockquote":2,"blockquotes":1,"blocks":22,"blog":1,"bm25":3,"bo":1,"bob":1,"body":5,"book":7,"bookmarks":1,"books":1,"booleans":1,"bootstrap":3,"bootstrapping":1,"bot":1,"both":23,"bother":1,"bots":1,"bottom":7,"bound":4,"boundary":3,"bounded":2,"box":4,"boxes":2,"brackets":2,"brain":3,"branch":4,"branching":1,"brand":1,"breach":1,"breadth":1,"break":4,"breaking":2,"breaks":2,"bridge":1,"bridged":1,"bridges":3,"brief":1,"briefly":2,"brittle":1,"broad":1,"broader":2,"broken":2,"brought":1,"browsable":1,"browse":1,"browser":12,"browsing":3,"bucket":1,"bug":3,"bugs":1,"build":27,"building":4,"builds":6,"built":18,"bulk":1,"bump":2,"bundle":1,"bundled":8,"burden":1,"bush":1,"but":28,"button":3,"by":44,"bypassed":1,"byte":3,"bytes":1,"c":5,"cabinet":1,"cache":15,"cached":3,"caches":1,"caching":3,"caddy":1,"call":4,"called":3,"callouts":2,"calls":2,"came":4,"can":46,"cancelable":1,"cancels":1,"candidate":3,"cannot":4,"canonical":10,"canvas":2,"cap":2,"capabilities":1,"capability":12,"capable":1,"capitalisation":1,"capping":1,"caps":3,"capture":3,"captured":1,"captures":1,"card":4,"cards":6,"care":9,"careful":1,"cares":1,"caret":1,"cargo":3,"carl":1,"carries":3,"carry":3,"carrying":1,"case":12,"cased":1,"cases":3,"catch":2,"catches":3,"catching":1,"categories":1,"category":2,"causal":1,"cause":1,"cdn":3,"ceiling":1,"century":1,"ceremony":1,"certain":1,"certainly":1,"chain":7,"chaining":1,"chains":1,"change":16,"changed":11,"changes":22,"changing":2,"channel":4,"characters":3,"chat":3,"cheap":2,"cheat":1,"check":10,"checking":1,"checklist":4,"checkout":1,"checks":2,"child":2,"children":2,"choice":3,"choices":1,"choose":1,"chooses":1,"chronological":2,"ci":8,"ciphertext":1,"citation":2,"citations":4,"cite":5,"cited":2,"cites":2,"citing":1,"claim":4,"claimed":2,"claims":2,"class":5,"classes":1,"classical":1,"classification":1,"claude":2,"clean":2,"cleanly":2,"cleanup":2,"clear":1,"cleared":1,"clears":1,"cli":25,"click":4,"clickable":1,"clicking":2,"clicks":2,"client":9,"clients":3,"climb":1,"clipboard":1,"clock":1,"clone":4,"clones":1,"close":3,"closest":2,"closing":1,"cloud":2,"cloudflare":1,"cloudfront":2,"cluster":1,"clusters":1,"clutter":1,"co":9,"coalesce":1,"coalesced":1,"coarse":2,"code":12,"codeberg":10,"codemirror":1,"coexist":1,"coexists":1,"cohort":2,"cohorts":2,"coined":1,"collab":11,"collaborating":1,"collaboration":2,"collaborative":2,"collaboratively":1,"collaborator":3,"collaborators":4,"collapsed":1,"collapses":1,"colleague":1,"colleagues":1,"collects":1,"collisions":1,"colour":3,"coloured":2,"colours":1,"column":3,"columns":2,"com":2,"combine":6,"combined":5,"combines":1,"combining":1,"come":2,"comes":4,"comfortable":2,"coming":1,"command":21,"commands":5,"comment":1,"comments":3,"commit":9,"commitments":1,"commits":8,"committed":2,"committing":3,"common":2,"commonmark":4,"commonplace":1,"communication":1,"communications":1,"community":2,"compare":3,"compares":1,"comparing":1,"comparison":1,"compatibility":2,"compatible":4,"compete":1,"competing":1,"compiled":3,"complaint":1,"complement":1,"complete":3,"completed":1,"completely":1,"completes":2,"completion":1,"completions":4,"components":2,"compose":2,"composed":1,"compound":1,"compounds":1,"comprehensive":1,"computer":1,"computes":1,"concept":6,"concepts":3,"conceptual":2,"conclude":2,"concluded":1,"concludes":2,"concluding":1,"conclusion":8,"conclusions":13,"concrete":5,"concurrently":1,"conditions":1,"config":8,"configuration":15,"configure":1,"configured":3,"confined":1,"confirm":2,"confirmed":2,"conflict":6,"conflicts":5,"confluence":1,"confuses":1,"conjunction":1,"connected":2,"connection":1,"connections":2,"connector":1,"connectors":1,"connects":1,"conrad":1,"consciously":1,"consequences":1,"considered":1,"consistent":2,"consistently":1,"console":1,"constantly":1,"constants":1,"constrained":1,"constraint":2,"constraints":1,"consult":1,"consulted":1,"consume":1,"consumers":1,"consumes":1,"contact":1,"contain":1,"container":2,"containerised":2,"containers":2,"containing":3,"contains":4,"content":20,"contents":3,"context":14,"continues":2,"continuously":2,"contract":4,"contractor":1,"contractors":1,"contracts":1,"contradiction":1,"contradictions":2,"contrary":1,"contribute":1,"contributed":4,"contributes":3,"contributing":1,"contribution":1,"contributors":1,"control":13,"controlled":1,"controls":1,"convenient":1,"convention":3,"conventional":2,"conventions":5,"converges":1,"cookies":1,"coordination":1,"copied":1,"copies":3,"copy":8,"copying":2,"core":5,"corner":1,"correctly":3,"correlates":1,"corresponding":1,"cosine":1,"cost":1,"could":5,"couldn":1,"count":6,"counterpart":1,"counts":3,"cover":1,"coverage":1,"covered":2,"covers":4,"coworker":1,"crack":1,"crash":2,"crashes":2,"crdt":11,"crdts":4,"create":11,"created":3,"creates":3,"creating":4,"creation":1,"credential":1,"credentials":1,"credited":1,"critical":2,"critically":1,"cross":6,"crucially":1,"cryptic":1,"cryptographic":2,"csp":1,"css":2,"ctrl":2,"cumulative":1,"cunningham":1,"curated":1,"curation":1,"curious":1,"current":10,"currently":3,"cursor":2,"custom":16,"customisation":1,"customising":13,"cut":1,"cutting":1,"cycle":1,"cytoscape":1,"d":7,"daemon":1,"daily":4,"dance":2,"dark":1,"dashboard":1,"dashboards":1,"dashes":1,"data":9,"database":9,"databases":1,"dataview":1,"date":5,"dates":3,"day":7,"days":2,"de":1,"dead":18,"debounced":1,"debouncing":1,"debugging":2,"decade":2,"decades":1,"decay":1,"decide":2,"decimal":3,"decision":5,"decisions":4,"declare":2,"declared":6,"declares":2,"declaring":3,"decorator":1,"decrease":1,"decrypt":2,"decrypting":1,"decryption":1,"decrypts":2,"dedicated":1,"deduplication":1,"deep":3,"default":38,"defaults":3,"defeasible":12,"defeasibly":6,"defeat":2,"defeated":3,"defeater":4,"defeaters":2,"defensive":1,"defined":3,"definitely":3,"definition":4,"degradation":1,"delegate":6,"delegated":1,"delete":11,"deleted":4,"deletes":3,"deleting":1,"deliberate":2,"deliberately":3,"deliverable":1,"delta":2,"demand":2,"demo":2,"dendron":4,"denied":1,"density":1,"deny":1,"denying":1,"deontic":2,"depend":1,"dependency":1,"deploy":2,"deployment":2,"deployments":2,"depth":4,"derivation":4,"derive":2,"derived":7,"derives":5,"described":3,"description":3,"deserves":2,"design":5,"designed":1,"desk":1,"desktop":1,"destructive":1,"detail":1,"details":4,"detect":1,"detection":2,"detects":3,"deterministic":5,"deterministically":1,"development":2,"device":4,"devices":2,"devtools":1,"diagnosis":1,"diagnostic":2,"diagnostics":4,"diagram":2,"dialog":1,"did":8,"didn":3,"dies":2,"diff":5,"difference":1,"different":10,"differently":1,"differing":1,"differs":2,"difficult":1,"diffing":2,"diffs":2,"dig":1,"dimensions":1,"dir":1,"direct":5,"directed":2,"directions":1,"directly":5,"directories":2,"directory":18,"disable":5,"disables":1,"disagree":2,"disambiguate":1,"disambiguation":1,"disappears":2,"disconnected":2,"disconnects":1,"discovery":1,"disguised":1,"disk":10,"disorganisation":1,"dispatch":1,"dispatched":1,"display":7,"displayed":2,"disposable":6,"distance":3,"distant":1,"distinct":2,"distinguishes":1,"distributing":1,"dive":2,"divides":1,"do":17,"doc":1,"docked":1,"docker":1,"docs":10,"document":3,"documentation":2,"documented":3,"documents":4,"does":25,"doesn":20,"doing":2,"dom":1,"domain":1,"don":21,"done":2,"door":1,"dot":3,"dotdir":2,"dotdirs":3,"dotfiles":1,"dotted":1,"double":1,"doubles":1,"down":3,"downgrade":1,"download":4,"downloads":1,"downstream":3,"dozen":1,"draft":5,"drafting":2,"drafts":1,"draw":2,"drawer":1,"drawn":1,"draws":4,"drift":2,"drifting":1,"drive":1,"driven":1,"drives":1,"drop":6,"dropbox":1,"dropped":4,"drops":1,"dsl":1,"dump":1,"dumping":2,"dumps":2,"duplicate":4,"duplicates":1,"duplicating":1,"durable":2,"duration":1,"during":6,"e":8,"each":28,"earlier":1,"early":1,"earned":1,"easier":2,"easy":1,"ecosystem":5,"ecosystems":9,"ed25519":5,"edge":4,"edges":4,"edit":18,"editable":1,"edited":2,"editing":17,"editor":17,"editors":3,"edits":9,"effect":1,"effective":1,"effects":1,"eight":1,"either":11,"element":2,"else":13,"elsewhere":1,"email":4,"embed":10,"embedded":5,"embedding":3,"embeddings":2,"embeds":11,"emergent":1,"emission":1,"emit":2,"emits":2,"emitted":1,"empty":6,"enable":3,"enabled":8,"enables":2,"enabling":1,"encode":1,"encoded":1,"encrypted":2,"encryption":2,"end":7,"endpoint":3,"endpoints":2,"ends":1,"enforces":1,"engine":8,"english":3,"enhancement":1,"enlarged":1,"enormous":2,"enough":7,"enter":4,"entered":1,"enterprise":1,"enters":1,"entire":3,"entirely":3,"entries":3,"entry":7,"enumerate":1,"env":3,"envelope":2,"environment":3,"environments":1,"envisioned":1,"ephemeral":5,"equivalent":3,"err":1,"error":10,"errors":7,"especially":1,"essay":3,"essays":1,"essence":1,"essential":1,"etc":8,"evaluate":1,"evaluates":1,"even":8,"event":5,"events":5,"eventual":1,"eventually":1,"ever":5,"evergreen":1,"every":47,"everyday":2,"everything":23,"everywhere":3,"evidence":3,"evolved":1,"evolving":1,"exact":6,"exactly":9,"example":15,"examples":3,"excalidraw":1,"exceeding":1,"excellent":2,"except":8,"exceptions":1,"excerpts":3,"excluded":4,"excludes":1,"excluding":1,"exclusion":4,"exclusive":2,"exec":1,"executable":5,"executables":1,"executing":1,"exercised":1,"exfiltration":1,"exist":11,"existed":2,"existing":7,"exists":7,"exit":5,"exits":6,"expand":1,"expanded":1,"expansion":1,"expect":2,"expected":3,"expecting":1,"experiment":1,"expire":1,"expired":1,"expiry":3,"explain":2,"explained":1,"explaining":1,"explains":1,"explanation":2,"explicit":2,"explicitly":3,"explore":1,"exploring":1,"export":16,"exported":1,"expose":4,"exposed":1,"exposes":8,"exposing":2,"exposure":1,"express":2,"expressed":1,"expresses":2,"expression":2,"expressions":2,"expressive":1,"ext":2,"extend":1,"extensibility":1,"extension":6,"extensions":3,"external":6,"extra":3,"extract":4,"extraction":1,"extracts":1,"extras":2,"eye":2,"eyeball":1,"eyes":1,"f":1,"face":1,"fact":7,"facto":1,"factor":1,"facts":10,"fail":2,"failed":2,"fails":7,"failure":2,"fair":2,"fall":1,"fallback":2,"falls":4,"false":1,"family":2,"famous":1,"faq":3,"far":2,"fast":8,"fastest":1,"fatal":2,"feature":17,"features":5,"feed":8,"feedback":1,"feeding":1,"feel":1,"feels":3,"fence":1,"fenced":8,"fences":4,"fetch":1,"few":14,"fido2":1,"field":3,"fields":12,"file":32,"filename":8,"filenames":2,"files":32,"filesystem":8,"filing":2,"fill":3,"filled":1,"fills":1,"filter":7,"filtered":2,"filtering":1,"filters":2,"final":3,"find":8,"finder":1,"finding":14,"finds":3,"fine":7,"finely":1,"finer":1,"fingerprint":1,"fingerprints":1,"finishes":1,"fire":5,"fired":2,"fires":3,"first":33,"fit":2,"fits":4,"five":5,"fix":8,"fixable":1,"fixed":3,"fixes":1,"fixture":3,"flag":23,"flagged":1,"flags":21,"flaky":1,"flash":2,"flat":3,"flavour":3,"flavours":3,"flaw":1,"flexibility":1,"flies":1,"flight":2,"flip":2,"flipped":1,"flipping":1,"flips":4,"floating":1,"flooding":1,"floor":2,"flow":5,"flowing":1,"flows":1,"flux":1,"fly":2,"foam":4,"focus":2,"focused":1,"folder":14,"folders":7,"folding":1,"follow":6,"followed":1,"following":10,"follows":2,"footnotes":2,"for":55,"forbid":1,"forbidden":1,"forbidding":1,"force":9,"forced":1,"forces":2,"forcing":1,"forever":2,"forged":1,"forgejo":2,"forgot":2,"forgotten":2,"fork":1,"forking":1,"form":12,"formal":4,"format":6,"formats":1,"formatting":1,"formed":1,"forms":3,"forward":13,"found":6,"foundational":1,"four":3,"fourth":1,"fragment":3,"fragments":1,"framed":1,"frames":1,"framing":1,"free":4,"freely":3,"frequency":1,"fresh":5,"friction":1,"friendlier":1,"friends":2,"from":49,"front":1,"frontmatter":24,"fs":2,"full":30,"fundamental":1,"funded":1,"further":2,"fusion":1,"future":4,"fuzzy":1,"g":7,"gains":1,"game":2,"gap":1,"gate":2,"gated":5,"gates":1,"gating":2,"general":2,"generate":3,"generated":6,"generates":5,"generating":2,"generation":2,"generator":1,"gentle":1,"genuine":2,"genuinely":4,"gephi":2,"german":1,"gessner":1,"gesture":1,"get":16,"gets":14,"getting":4,"git":14,"github":8,"gitignore":7,"gitlab":2,"give":3,"given":4,"gives":7,"giving":2,"glance":4,"glass":2,"glob":6,"global":4,"globally":1,"globs":2,"glossary":4,"glyph":1,"go":8,"goal":2,"goals":2,"goes":5,"going":1,"golden":1,"gone":1,"good":11,"google":1,"got":2,"graceful":1,"grained":1,"grammar":1,"grammatical":1,"grant":2,"grantee":1,"grantor":1,"grants":3,"graph":40,"graphology":1,"graphs":2,"graphviz":1,"grasp":1,"green":1,"grep":2,"grew":2,"grid":3,"grocery":1,"ground":2,"grounded":1,"grounding":1,"grounds":1,"group":3,"grouping":1,"groups":2,"grows":4,"guarantee":1,"guard":1,"guards":1,"gui":2,"guide":6,"guides":1,"h":1,"habit":1,"habits":1,"had":1,"half":3,"hamming":3,"hand":9,"handful":6,"handles":3,"handling":1,"handoff":2,"handshake":1,"handy":3,"hangs":1,"happen":4,"happened":1,"happens":4,"hard":2,"hardcoded":3,"hardware":3,"harness":2,"harrison":1,"has":25,"hash":12,"hashed":1,"hashes":5,"hashing":1,"hashtags":1,"hasn":2,"have":28,"haven":2,"havent":1,"hawaiian":1,"he":1,"head":4,"header":3,"headers":2,"heading":10,"headings":9,"headless":3,"health":3,"healthy":1,"hear":1,"heart":1,"heavier":1,"hedge":1,"hello":2,"help":2,"helper":1,"helpful":2,"here":9,"heritage":2,"heuristic":1,"heuristics":1,"hidden":3,"hierarchical":1,"hierarchy":1,"high":1,"higher":3,"highest":3,"highlighted":1,"highlights":1,"hint":2,"hints":1,"his":1,"historical":4,"history":15,"hit":3,"hmac":1,"hold":4,"holding":4,"holds":6,"honest":1,"honoured":1,"honours":2,"hood":10,"hook":15,"hooking":1,"hooks":21,"hop":7,"hope":1,"hops":6,"host":5,"hostable":1,"hosted":1,"hostile":1,"hosting":1,"hostname":3,"hosts":1,"hotkeys":1,"hour":1,"hours":2,"house":1,"how":29,"however":2,"hr":1,"html":8,"http":4,"https":15,"huge":1,"human":7,"humans":3,"hundred":2,"hunting":1,"hurry":1,"hygiene":1,"hypertext":1,"hyphenated":1,"hypothetical":2,"hypotheticals":1,"i":8,"icloud":1,"id":11,"idea":8,"ideal":2,"ideas":3,"identical":3,"identifier":1,"identifiers":1,"identifies":1,"identities":1,"identity":4,"ides":1,"idioms":1,"idle":1,"ids":3,"if":50,"ignore":6,"ignored":2,"ignores":1,"image":1,"images":1,"imagine":1,"immediately":4,"implementation":2,"implemented":2,"implicit":2,"implicitly":1,"import":3,"important":1,"imported":1,"impose":1,"impossible":1,"in":55,"inbound":6,"include":10,"included":1,"includes":4,"including":9,"incoming":1,"incompatible":1,"incorrectly":1,"increase":1,"increases":1,"incremental":5,"increments":1,"indentation":1,"independent":1,"independently":1,"index":22,"indexed":1,"indexes":2,"indexing":7,"indifferent":1,"indispensable":1,"individual":1,"inference":1,"infers":1,"information":1,"inherent":1,"inherits":2,"init":1,"initialises":1,"injection":1,"inline":6,"inlined":1,"input":2,"inputs":1,"ins":1,"insensitive":4,"inserted":1,"inside":16,"insight":1,"inspect":3,"inspects":1,"inspired":1,"install":20,"installation":24,"installed":9,"installer":2,"installs":1,"instance":1,"instant":1,"instantly":2,"instead":11,"instructions":1,"integration":4,"intellectual":2,"intend":2,"intended":1,"intent":1,"intentional":1,"intentionally":1,"interact":1,"interaction":3,"interactive":2,"intercepts":1,"interesting":4,"interleave":1,"interleaved":1,"internally":1,"internals":1,"internet":2,"interpret":1,"intimate":1,"into":34,"intricate":1,"intro":1,"introduces":1,"introspect":1,"invalid":2,"invalidate":2,"invalidates":2,"invariants":2,"invent":1,"invented":1,"inverse":2,"inverses":1,"invisible":2,"invitation":4,"invitations":8,"invite":6,"invitee":1,"inviter":3,"invites":1,"inviting":1,"invocation":1,"invoke":1,"invoked":2,"invokes":2,"involved":1,"inward":1,"io":7,"is":54,"isbns":1,"ish":1,"isn":8,"iso":3,"isolate":1,"isolated":1,"isolation":1,"issue":9,"issued":1,"issues":3,"issuing":2,"it":54,"item":2,"items":1,"its":31,"itself":6,"j":1,"january":1,"javascript":2,"jinja2":1,"jj":7,"jobs":1,"johnny":2,"join":1,"journal":6,"journaling":1,"jq":1,"js":3,"json":24,"jujutsu":3,"july":1,"jump":3,"junction":1,"june":1,"just":27,"justification":1,"jwt":1,"jwts":1,"katex":1,"keep":17,"keeping":4,"keeps":6,"kept":1,"key":16,"keybindings":2,"keyed":2,"keypair":1,"keys":12,"keystroke":1,"keystrokes":1,"keyword":2,"killed":2,"kind":3,"kinds":1,"knew":1,"knob":2,"know":10,"knowing":2,"knowledge":10,"known":2,"knows":2,"kommunikation":1,"label":1,"labelled":4,"labels":3,"laid":2,"land":2,"landed":1,"landing":1,"lands":2,"language":8,"laptop":5,"large":5,"larger":1,"largest":1,"last":10,"late":2,"later":16,"latest":5,"launched":1,"launches":2,"layer":13,"layered":2,"layers":2,"layout":9,"layouts":1,"lead":1,"leaders":1,"leading":2,"leaf":1,"leak":2,"leaked":1,"lean":1,"learn":2,"learning":1,"least":3,"leave":5,"leaves":5,"leaving":4,"left":3,"legal":3,"legitimate":1,"length":1,"less":4,"let":6,"lets":8,"letter":1,"letting":2,"leuf":1,"level":13,"liberally":1,"libraries":1,"library":2,"license":2,"life":3,"lifecycle":15,"lifts":1,"light":1,"lightweight":2,"like":20,"limit":1,"limits":2,"line":19,"lineage":1,"liner":2,"liners":1,"lines":7,"link":35,"linked":11,"linker":1,"linking":12,"links":31,"linnaeus":1,"linux":5,"lisp":5,"list":30,"listed":2,"listens":1,"listing":1,"lists":11,"literal":5,"literals":5,"literary":1,"little":2,"live":23,"lived":2,"lives":18,"living":1,"ll":12,"llm":2,"load":1,"local":14,"localhost":2,"locality":3,"locally":4,"location":2,"lock":1,"locked":1,"locks":3,"log":6,"logging":2,"logic":5,"logical":1,"login":3,"logins":1,"logs":2,"logseq":7,"long":12,"longer":3,"look":20,"looks":5,"lookup":1,"loop":7,"loopback":1,"loose":1,"loosely":1,"lose":3,"loses":1,"losing":2,"lost":2,"loudly":1,"lower":5,"lowest":2,"lua":2,"luhmann":2,"m":1,"mac":1,"machine":6,"machines":2,"macos":5,"made":7,"magic":2,"main":10,"make":7,"makes":9,"making":2,"malformed":2,"malicious":2,"man":4,"manage":3,"manageable":1,"management":1,"manager":3,"managing":1,"mandatory":1,"manifest":5,"manifests":1,"manual":1,"manually":3,"many":11,"map":4,"march":1,"mark":3,"markdown":26,"marked":1,"markers":1,"marketplace":1,"markup":1,"match":15,"matcher":1,"matches":13,"matching":9,"material":3,"materialises":1,"mathematical":1,"mathematically":1,"maths":1,"matrix":3,"matter":11,"matters":7,"max":3,"may":8,"mcp":8,"md":3,"mdast":1,"mdbook":7,"mean":5,"meaning":8,"meaningful":5,"means":10,"meant":2,"mechanised":1,"mechanism":1,"media":1,"mediawiki":1,"meeting":6,"memex":1,"memory":2,"mention":1,"mentions":3,"merge":6,"merged":1,"merges":5,"merging":1,"merkle":4,"mermaid":1,"message":6,"messages":2,"meta":1,"metadata":9,"metal":1,"method":8,"microfilm":1,"micropayment":1,"mid":5,"middle":4,"might":5,"migrate":1,"migrating":6,"migration":2,"million":1,"millisecond":1,"min":2,"mind":2,"mini":4,"minijinja":2,"minimal":6,"minimum":1,"minus":4,"minute":2,"minutes":1,"mirrors":1,"mis":1,"misbehaves":1,"mismatches":1,"miss":2,"missed":1,"missing":8,"mistake":1,"mit":1,"mitsuhiko":1,"mix":1,"mixed":3,"mixes":1,"mnemonic":7,"moc":1,"mocs":1,"modal":3,"mode":16,"model":14,"modelcontextprotocol":1,"models":3,"modern":1,"modified":2,"modifies":3,"modify":3,"module":1,"moment":2,"monday":1,"month":5,"months":3,"more":15,"morning":1,"mortems":1,"most":18,"mostly":1,"motivated":2,"motivates":1,"mounted":2,"move":4,"moved":3,"moves":2,"moving":1,"mtime":5,"mtimes":1,"much":1,"multi":15,"multiple":5,"must":5,"mutate":1,"mutating":1,"mutations":1,"mutually":1,"my":4,"mysteries":1,"n":2,"nag":1,"name":25,"named":5,"names":8,"naming":2,"narrative":3,"narrow":6,"narrower":4,"narrowing":1,"narrows":1,"native":2,"natural":3,"naturally":1,"navigable":1,"navigate":2,"navigates":2,"navigating":3,"navigation":6,"nd":1,"ndjson":2,"near":1,"nearest":2,"nearly":3,"need":20,"needed":4,"needs":6,"negatable":1,"negating":1,"negation":1,"negative":1,"neighbours":1,"neither":6,"nelson":2,"nested":3,"nesting":1,"net":1,"netlify":1,"network":7,"neutral":2,"never":23,"new":18,"newer":1,"newly":2,"next":13,"nginx":2,"niklas":1,"nine":1,"no":46,"nobody":1,"node":5,"nodes":2,"noise":3,"noisy":2,"nomad":1,"non":9,"nonce":1,"nonces":2,"none":6,"nonexistent":1,"normal":5,"normalise":1,"normalised":2,"normally":5,"not":43,"notation":3,"note":26,"notes":30,"nothing":15,"notice":1,"notifications":1,"notify":1,"notion":1,"notoriously":1,"november":1,"now":8,"nudge":1,"null":1,"number":7,"numbered":3,"numbers":4,"o":1,"object":5,"objects":2,"obligation":1,"obligatory":1,"observed":1,"obsidian":13,"obvious":3,"occasionally":1,"of":54,"off":11,"office":1,"official":1,"offline":5,"often":7,"og":1,"old":8,"older":4,"omit":1,"omitted":1,"omitting":1,"on":53,"onboarding":1,"once":17,"one":44,"ones":15,"ongoing":1,"online":1,"only":35,"onnx":1,"onto":1,"onwards":1,"op":1,"open":22,"opened":1,"opening":1,"opens":7,"operate":2,"operates":2,"operation":2,"operational":2,"operations":1,"operator":5,"operators":2,"opposite":2,"opt":4,"optional":20,"optionally":3,"options":4,"opts":1,"or":54,"order":6,"ordering":3,"org":8,"organise":1,"organising":7,"orientation":1,"oriented":1,"origin":4,"original":4,"originals":1,"originated":1,"orphan":5,"orphaned":1,"orphans":12,"os":2,"other":21,"others":5,"otherwise":8,"out":24,"outages":1,"outbound":6,"outdated":1,"outgoing":1,"outlives":1,"output":19,"outside":4,"outstanding":2,"over":25,"overhead":1,"overlapping":2,"overlay":1,"overlays":1,"overridable":1,"overridden":1,"override":6,"overrides":3,"overriding":1,"overview":19,"overwhelming":1,"own":18,"owner":4,"ownership":2,"owns":1,"p":1,"package":1,"packages":1,"page":50,"pages":39,"pair":3,"pairing":1,"pairs":1,"pandoc":8,"pane":8,"panel":5,"panels":1,"panes":1,"paper":7,"paragraph":10,"paragraphs":3,"parallelisable":1,"parameters":1,"parent":2,"parenthesised":1,"parse":5,"parsed":8,"parser":6,"parsers":1,"parses":5,"parsing":4,"part":7,"partial":2,"partially":1,"particular":2,"partner":1,"parts":1,"party":5,"pass":10,"passage":2,"passed":2,"passes":3,"passive":1,"passkey":9,"passkeys":8,"password":3,"passwords":2,"past":12,"paste":8,"pasted":1,"path":20,"paths":11,"pattern":3,"patterns":6,"pause":1,"pay":2,"payload":4,"payloads":1,"payoff":1,"pc":1,"penguin":1,"penguins":1,"people":9,"per":24,"percent":1,"perfectly":1,"performs":1,"period":1,"periodic":1,"peritext":3,"permanence":1,"permanent":3,"permanently":1,"permission":3,"permitted":1,"permitting":1,"persist":2,"persistent":5,"persists":1,"person":2,"personal":2,"philosophy":1,"phished":1,"phishing":1,"phone":1,"phrase":8,"pick":11,"picker":3,"picks":5,"picture":1,"piece":2,"pieces":1,"pill":1,"pin":2,"ping":1,"pings":1,"pinned":1,"pinning":1,"pins":1,"pipe":9,"piped":3,"pipeline":13,"pipes":1,"piping":1,"place":6,"placeholders":2,"placement":1,"placements":1,"places":3,"plagiarism":1,"plain":18,"plan":3,"plane":1,"planned":3,"planning":1,"plans":1,"platform":1,"play":1,"plays":1,"plug":2,"plugin":9,"plugins":4,"plus":10,"point":22,"pointed":1,"pointer":1,"pointers":1,"pointing":6,"points":16,"pointy":1,"policies":1,"policy":2,"politely":1,"poll":1,"polls":1,"popularised":1,"port":4,"portable":2,"position":1,"positive":1,"positives":1,"possible":2,"possibly":2,"post":7,"posted":1,"posters":1,"posting":1,"posts":1,"posture":1,"pour":1,"power":2,"powers":1,"pr":1,"practical":2,"practice":4,"practitioner":1,"pragmatic":1,"pre":6,"prebuilt":1,"precedence":3,"precision":1,"predates":1,"predicate":1,"predictable":1,"prefer":5,"preference":2,"preferences":1,"prefix":7,"prefixed":2,"prelude":1,"premise":1,"premises":1,"preprocessor":1,"preprocessors":2,"present":3,"presentation":1,"preserve":1,"preserved":2,"preserves":1,"preserving":1,"pressure":1,"prevents":1,"preview":4,"previewing":4,"previous":1,"primary":2,"primer":1,"primitive":1,"principles":1,"print":3,"printed":3,"prints":13,"priority":1,"private":5,"probably":3,"probe":2,"probing":1,"problem":3,"problems":1,"proceed":1,"proceeds":1,"process":4,"processes":1,"processing":1,"produce":3,"produced":1,"produces":4,"product":1,"profile":1,"program":1,"programmatic":1,"programmatically":1,"prohibition":2,"project":15,"projects":1,"promise":1,"promote":2,"prompt":2,"prompted":1,"prompts":3,"prone":1,"proof":9,"proofreading":1,"proper":1,"properties":1,"property":1,"proposal":2,"propose":1,"proprietary":1,"prose":10,"protect":1,"protocol":6,"provable":5,"prove":1,"proved":3,"provenance":5,"provides":2,"provisional":1,"proxy":1,"prune":1,"pubkey":2,"public":6,"publication":3,"publicly":1,"publish":8,"published":4,"publishing":3,"pull":3,"pulling":1,"pulls":4,"punctuation":1,"pure":1,"purpose":8,"push":6,"pushes":1,"put":10,"puts":1,"px":1,"py3":1,"python":1,"qr":1,"quarto":1,"queries":18,"query":18,"queryable":2,"question":5,"questions":7,"quick":9,"quickadd":1,"quiet":3,"quieter":1,"quirks":1,"quit":1,"quote":4,"quoted":3,"quotes":1,"quoting":1,"rail":5,"raise":3,"ran":2,"range":1,"rank":1,"ranked":1,"rapid":1,"rare":2,"rarely":1,"rather":5,"ratio":2,"raw":6,"re":32,"reach":6,"reachable":3,"reaches":2,"read":26,"readable":7,"reader":8,"readers":2,"readiness":3,"reading":10,"reads":13,"ready":5,"real":17,"realistic":1,"really":1,"rearrange":1,"rearranged":1,"reason":11,"reasoner":1,"reasoning":20,"reasons":2,"rebuild":5,"rebuilds":5,"recall":1,"receive":1,"received":1,"receives":5,"recent":3,"recipes":1,"recipient":2,"reciprocal":1,"recognise":1,"recognised":1,"recognises":1,"recommended":1,"recomputed":1,"recomputes":1,"reconnect":1,"reconnected":1,"reconnection":1,"reconstruct":2,"reconstructed":1,"record":3,"records":3,"recover":3,"recoverable":1,"recovering":1,"recovers":1,"recovery":5,"recursion":2,"recursively":2,"red":2,"redaction":1,"redeem":1,"redeemed":1,"redeploy":4,"redirect":2,"redirected":3,"redirects":2,"redis":1,"reducers":1,"ref":2,"refactor":3,"refer":1,"reference":18,"referenced":1,"references":7,"referencing":2,"referential":2,"referred":1,"refresh":3,"refreshing":1,"refuse":2,"refuses":1,"regardless":4,"regenerate":1,"regenerates":1,"regex":3,"region":3,"register":4,"registered":2,"registering":1,"registers":1,"registration":2,"regular":2,"reindex":2,"reindexing":1,"reinstall":1,"rejected":1,"rejects":1,"related":53,"relations":1,"relative":4,"relaxed":1,"release":6,"released":1,"relevant":2,"reload":4,"reloads":1,"rely":1,"relying":3,"remain":2,"remains":1,"remark":8,"remediations":2,"remember":4,"remembered":1,"remembers":1,"remote":1,"remove":5,"removed":3,"removes":1,"rename":8,"renamed":1,"renaming":2,"render":20,"rendered":13,"renderer":1,"rendering":6,"renders":7,"renumbering":1,"reorder":1,"reorganise":1,"reorienting":1,"reparse":2,"reparses":3,"reparsing":1,"repeatable":5,"repetition":2,"replace":4,"replaced":1,"replacement":1,"replayed":2,"replicated":2,"repo":13,"report":4,"reported":1,"reports":5,"repository":1,"represents":1,"reproduces":1,"reproducible":2,"reproducing":1,"request":3,"requested":1,"requests":1,"require":8,"required":12,"requirement":1,"requires":22,"rerun":1,"rescan":3,"research":12,"researching":1,"reserved":4,"resident":1,"resize":1,"resolution":5,"resolve":13,"resolved":1,"resolver":2,"resolves":5,"response":1,"rest":11,"restart":1,"restarted":1,"rested":1,"restraint":1,"restrict":2,"restricted":1,"restriction":1,"restrictive":1,"restricts":1,"restyle":1,"restyling":1,"result":5,"resulting":4,"results":4,"resync":1,"retrace":2,"retract":1,"retrospectives":1,"return":2,"returned":1,"returns":10,"reuse":1,"reused":4,"revealed":1,"reverse":4,"revert":1,"reverts":1,"review":4,"reviewer":2,"reviewers":1,"reviewing":1,"reviews":1,"revise":1,"revised":1,"revision":1,"revisiting":1,"revocation":1,"revoke":3,"revoked":2,"revoking":1,"rewrite":4,"rewrites":1,"rewriting":4,"rewritten":1,"rewrote":2,"rfc":1,"rhythm":1,"rich":1,"richer":2,"right":17,"rigid":1,"river":1,"roam":1,"roff":1,"role":2,"roles":3,"roll":1,"rolling":1,"room":2,"root":12,"rot":2,"rotate":4,"rotation":1,"rough":1,"roughly":1,"route":1,"routed":1,"row":3,"rs":2,"rss":3,"rsync":1,"rule":14,"rules":15,"run":37,"runaway":1,"running":27,"runs":18,"runtime":4,"runtimes":1,"rust":5,"rustup":2,"s":48,"s3":2,"saas":1,"safe":8,"safely":2,"safety":3,"said":3,"salt":2,"same":37,"sample":1,"save":15,"saved":3,"saves":4,"saw":2,"say":6,"saying":1,"says":6,"scaffold":2,"scaffolding":1,"scale":4,"scales":1,"scaling":1,"scan":7,"scanned":1,"scanner":3,"scans":4,"scenarios":1,"schema":8,"scientific":1,"scope":8,"scoped":5,"scopes":1,"scoping":4,"score":1,"scrambles":1,"scrapers":1,"scraping":1,"scratch":4,"screen":1,"script":4,"scripting":1,"scripts":6,"scroll":1,"sealed":1,"search":18,"searchable":1,"searches":2,"searching":11,"second":10,"seconds":5,"secret":5,"secrets":2,"section":9,"sections":1,"security":3,"see":54,"seed":4,"seeded":2,"seeds":1,"seeing":2,"seen":1,"sees":6,"segment":1,"selected":1,"selection":1,"selective":1,"selector":3,"selectors":2,"self":5,"semantic":6,"semantics":1,"semver":1,"send":3,"sense":2,"sensible":1,"sensitive":6,"sent":1,"sentence":3,"separate":8,"separately":1,"sequence":1,"serialised":3,"serve":10,"server":27,"servers":1,"serves":1,"serving":1,"session":7,"sessions":3,"set":19,"sets":4,"settable":1,"setting":1,"settings":4,"settle":1,"settled":1,"setup":3,"several":4,"sftp":1,"sha":1,"shallower":1,"shape":6,"shaped":4,"shapes":2,"shard":1,"share":8,"shared":7,"shares":3,"sharing":3,"shas":1,"sheet":1,"shell":14,"shells":1,"shim":2,"ship":5,"shipped":2,"ships":4,"short":12,"shortest":6,"shorthand":1,"should":10,"shouldn":2,"show":9,"showing":4,"shown":1,"shows":11,"shutdown":1,"sibling":3,"side":13,"sidebar":8,"sidecar":3,"sides":3,"sight":1,"sigma":3,"sign":3,"signal":5,"signals":1,"signature":2,"signed":4,"signing":6,"signs":1,"silence":1,"silencing":1,"silent":4,"silently":1,"simhash":4,"similar":9,"similarity":2,"simplest":1,"simulate":1,"simultaneously":1,"since":2,"single":15,"sit":1,"site":19,"sits":3,"sitting":1,"situation":2,"situations":1,"six":3,"sixty":1,"size":4,"skeleton":1,"skim":2,"skip":5,"skipped":5,"skips":4,"slack":2,"sleep":1,"slightly":1,"slip":3,"slips":1,"slot":1,"slow":2,"slug":3,"slugification":1,"slugified":2,"slugifies":1,"slugs":2,"small":18,"smaller":1,"smallest":1,"smart":1,"smoothness":1,"snappy":1,"snaps":1,"snapshot":15,"snapshots":9,"snapshotting":3,"snippet":4,"so":32,"social":2,"sociologist":1,"soft":2,"software":1,"solo":1,"solution":1,"solves":1,"solving":1,"some":12,"someone":6,"something":14,"sometimes":1,"somewhere":4,"sort":1,"sorts":1,"sounds":1,"source":20,"sources":5,"spa":4,"spaced":2,"spaces":3,"spake2":3,"sparingly":1,"sparkline":1,"spawn":2,"spawning":1,"spawns":1,"speak":1,"speaking":1,"spec":1,"special":2,"specific":21,"specifically":1,"specification":1,"speed":1,"spell":1,"spelling":1,"spend":1,"spent":1,"spindle":6,"spl":23,"splblock":1,"split":1,"splits":1,"spoken":1,"spot":2,"spotting":1,"sprinkled":2,"spuriously":1,"sqlite":2,"src":2,"ssh":4,"stabilise":1,"stable":10,"stack":5,"stage":4,"stages":3,"staging":2,"stakes":1,"stale":5,"stance":1,"stand":2,"standalone":2,"standard":5,"standardised":1,"start":17,"started":4,"starter":1,"starting":4,"starts":9,"startup":1,"stash":1,"state":15,"stated":2,"states":1,"static":20,"statistics":1,"stats":2,"status":3,"statuses":1,"stay":12,"staying":1,"stays":10,"stderr":4,"stdin":7,"stdio":2,"stdout":6,"steagall":1,"steal":1,"stem":3,"step":4,"steps":3,"sticky":1,"still":21,"stolen":1,"stomping":1,"stood":1,"stop":3,"stopped":1,"stopping":1,"stops":3,"storage":2,"store":8,"stored":4,"stores":1,"storing":1,"story":2,"straight":5,"stream":1,"streamed":1,"strength":1,"strengths":1,"strict":6,"stricter":1,"strictly":1,"strikethrough":1,"string":8,"strings":3,"strip":3,"stronger":5,"structural":1,"structurally":1,"structure":6,"structured":7,"stub":3,"stubs":1,"studies":1,"studiorum":1,"style":5,"subcommand":5,"subcommands":4,"subdirectory":2,"subfolders":1,"subject":3,"subprocess":4,"subprocesses":1,"subsequent":5,"subset":1,"substitute":1,"subtraction":1,"such":3,"suggested":1,"suggesting":1,"suitable":1,"suits":2,"summarises":1,"summary":4,"sunday":1,"superset":1,"supplement":1,"support":4,"supported":6,"supporting":1,"supports":2,"suppress":2,"sure":1,"surface":10,"surfaced":3,"surfaces":5,"surgically":1,"surprise":2,"surprisingly":1,"surrounding":5,"survive":2,"survives":3,"suspect":1,"suspend":1,"svg":3,"swap":1,"swapped":2,"sweep":1,"sweet":1,"switch":2,"switched":1,"switcher":1,"switches":3,"switching":3,"symbols":2,"symptom":1,"sync":9,"syncing":1,"syncthing":1,"syntax":21,"syntaxes":1,"system":4,"sönke":1,"t":34,"tab":4,"table":13,"tables":5,"tabular":1,"tag":6,"tagged":4,"tagging":1,"tags":9,"tail":1,"take":5,"takedown":1,"taken":1,"takes":7,"taking":3,"talk":1,"talks":1,"tangling":1,"tantivy":1,"tap":1,"tarball":1,"target":10,"targets":3,"task":6,"tasks":1,"taxonomy":1,"tcp":1,"team":15,"teams":1,"technology":1,"ted":2,"tedious":1,"tell":3,"tells":9,"template":8,"templater":1,"templates":9,"temporal":3,"temporarily":1,"tempted":1,"ten":3,"tenant":1,"tend":2,"tends":1,"tens":1,"tentative":1,"term":3,"terminal":17,"terminals":1,"test":5,"testing":1,"tests":2,"text":23,"textbook":1,"than":17,"thank":1,"that":49,"the":55,"their":14,"theirs":1,"them":36,"theme":19,"themes":9,"theming":3,"themselves":1,"then":21,"theories":1,"theory":9,"there":22,"these":18,"they":31,"thing":10,"things":12,"think":7,"thinking":8,"thinks":2,"thinner":1,"third":2,"thirty":2,"this":45,"thomas":1,"those":16,"though":2,"thought":9,"thoughts":2,"thousands":2,"thrashing":1,"threaded":1,"threads":1,"threat":2,"three":25,"threshold":2,"through":19,"throughout":1,"thrown":1,"thumb":1,"thumbs":1,"tidiness":1,"tied":1,"tier":4,"tightly":1,"time":40,"timeline":5,"timeout":1,"timeouts":1,"times":2,"timestamp":1,"timestamped":1,"timestamps":1,"tip":1,"title":7,"titled":1,"titles":4,"to":55,"today":2,"todo":1,"tofu":1,"together":3,"toggle":2,"token":5,"tokenisers":1,"tokens":6,"tolerate":2,"toml":1,"tomorrow":2,"too":8,"took":1,"tool":18,"toolchain":4,"tooling":3,"tools":8,"top":13,"topic":2,"topical":1,"topics":1,"topology":1,"total":1,"touch":10,"touched":3,"touches":1,"touching":4,"toward":2,"traces":2,"tracing":1,"track":4,"tracked":1,"tracker":1,"tracking":2,"tracks":2,"tradeoff":2,"tradition":1,"trail":6,"trails":2,"transclude":3,"transcluded":1,"transclusion":14,"transform":2,"transforming":1,"transforms":3,"transitive":3,"translates":1,"translation":2,"transport":1,"transports":1,"travel":18,"traversal":4,"traversals":1,"traverse":2,"treat":4,"treatment":1,"treats":4,"tree":14,"trees":8,"trend":2,"tried":1,"tries":1,"trigger":4,"triggered":1,"triggering":2,"triggers":2,"trimmed":1,"trivial":1,"trivially":1,"troubleshooting":3,"true":4,"truly":1,"trumps":1,"trust":3,"trusts":1,"truth":5,"try":6,"trying":2,"ttl":1,"tty":4,"tuesday":2,"tunable":1,"tune":2,"tuned":1,"turn":2,"turns":9,"tweety":2,"twenty":2,"two":37,"type":9,"typed":5,"types":3,"typescript":1,"typical":5,"typically":1,"typing":2,"typo":2,"typography":1,"typos":3,"ui":12,"un":1,"unavailable":1,"unblocked":1,"unbounded":1,"unbreakable":1,"unchanged":4,"unconditionally":1,"undefined":1,"under":22,"underlying":3,"underneath":1,"understand":1,"understanding":1,"understands":1,"undirected":1,"undo":1,"uneditable":1,"unexpected":1,"unique":3,"uniqueness":1,"unit":2,"units":1,"unknown":1,"unless":3,"unlike":2,"unlimited":1,"unlocked":1,"unlocks":2,"unprovable":1,"unreachable":1,"unreadable":1,"unresolved":1,"unscoped":1,"until":4,"untouched":4,"untrusted":2,"up":28,"update":4,"updated":2,"updates":3,"updating":1,"upgrade":2,"upgrading":1,"upload":2,"upstream":1,"url":14,"urls":11,"us":2,"usable":1,"use":40,"used":10,"useful":18,"user":17,"username":2,"users":3,"uses":19,"using":8,"usual":3,"usually":6,"v":1,"v0":4,"v1":2,"vague":1,"vaguely":1,"valid":5,"validate":1,"validated":1,"validation":1,"validator":1,"value":7,"values":1,"vanilla":1,"vanishes":1,"vannevar":1,"var":2,"variable":4,"variables":5,"variant":1,"variants":1,"vars":2,"vault":53,"vaults":18,"vcs":6,"ve":6,"vector":1,"verbatim":2,"verbosity":1,"vercel":1,"verifies":2,"verify":1,"verifying":1,"versa":1,"version":10,"versioned":1,"versions":1,"very":3,"via":22,"vice":1,"view":12,"viewer":8,"viewing":2,"viewport":1,"views":2,"vim":1,"violate":1,"violation":1,"visible":2,"vision":1,"visual":1,"visualisation":1,"visually":1,"viz":1,"vm":1,"vocabulary":1,"volume":1,"vpn":1,"vs":8,"w":1,"w3c":1,"waiting":1,"waits":1,"wal":1,"walk":8,"walked":1,"walking":1,"walks":6,"wall":1,"want":35,"wanted":2,"wants":1,"ward":1,"warning":3,"warnings":3,"warns":1,"was":10,"watch":3,"watcher":1,"watching":8,"way":16,"ways":4,"we":4,"web":23,"webauthn":6,"webgl":1,"webhook":2,"website":1,"websocket":5,"websockets":1,"week":1,"weekend":1,"weekly":2,"weeks":1,"weigh":1,"weight":1,"weighted":1,"welcome":1,"well":6,"went":2,"were":9,"what":47,"whatever":12,"when":47,"whenever":2,"where":27,"wherever":1,"whether":8,"which":28,"whichever":2,"while":8,"who":9,"whoever":1,"whole":18,"whom":1,"whose":15,"why":22,"wide":2,"widely":1,"widen":1,"widens":1,"widget":8,"width":1,"wiki":4,"wikilink":20,"wikilinks":26,"wikipedia":1,"wikis":1,"wikiwikiweb":1,"wildcard":1,"will":20,"win":2,"window":2,"windows":8,"winner":1,"wins":6,"wiped":2,"wire":5,"wires":1,"wiring":2,"wish":2,"with":52,"withdrawn":1,"within":5,"without":38,"won":8,"wont":1,"word":8,"words":2,"work":23,"worked":4,"workers":2,"workflow":11,"workflows":4,"workhorse":1,"working":10,"works":17,"workspace":2,"world":2,"worth":3,"would":8,"wrap":3,"write":23,"writer":2,"writers":6,"writes":14,"writing":31,"written":10,"wrong":6,"wrote":13,"ws":1,"x":5,"x86":3,"xanadu":4,"xml":1,"y":2,"yaml":10,"yank":1,"year":2,"years":2,"yes":5,"yesterday":1,"yet":8,"yields":1,"you":53,"your":51,"yours":3,"yourself":3,"yubikey":3,"zealous":1,"zero":10,"zetl":50,"zettelkasten":9,"zettelkästen":1,"zip":3,"zippered":1},"docs":[{"dl":512,"n":"Customising the Look","s":"reading/customising-the-look","secs":[{"h":"Customising the Look","l":6,"t":"Both [[Web Server]] and [[Static Site Export]] accept `--theme <name>`. A theme is a folder under `.zetl/themes/<name>/` containing Minijinja templates, static assets, and an optional `theme.toml`. You only override what you want — everything you don't ship falls back to the built-in defaults."},{"h":"Why themes matter","l":10,"t":"The default theme is deliberately neutral so it works as a starting point, not an endpoint. You will probably want to change at least the colours, the typography, and the name at the top of the sidebar. Most of that is CSS-only: zetl exposes a versioned set of CSS custom properties so you can restyle the graph widget, the shell, and page typography without touching any HTML. The bigger reason the theming story is careful: zetl's graph widget is a single, persistent Sigma.js instance that survives page navigation (no flash, no re-layout). Themes that rewrite `base.html` have to preserve two DOM markers to keep that property — otherwise the graph re-initialises on every click. The contract below is the bargain that makes \"persistent graph across a multi-page site\" possible."},{"h":"Creating a theme","l":16,"t":"Bundled themes are built into the binary. Export one to the vault to start editing: Then point `serve` or `build` at it:"},{"h":"What lives where","l":32,"t":"All templates use [Minijinja](https://github.com/mitsuhiko/minijinja) (Jinja2-compatible). Child templates `{% extends \"base.html\" %}` and override blocks; missing templates fall back to built-ins."},{"h":"CSS-only restyling","l":50,"t":"For most visual changes you don't need to override any HTML. The default theme exposes CSS custom properties for every colour, size, and layout track the graph and shell use. Override them in your own `static/theme.css`: The full property list (`--zetl-graph-*`, `--zetl-graph-widget-*`, `--zetl-shell-*`) lives in [[Configuration]]. Sigma reducers read the properties via `getComputedStyle` and refresh on `prefers-color-scheme` or `data-theme` changes, so a dark-mode toggle is a CSS class flip, not a theme rebuild."},{"h":"`theme.toml`","l":65,"t":"The config file groups settings into tables:"},{"h":"Graph placements","l":83,"t":"The persistent graph mini-map has three placements, switched by a single `theme.toml` key: | `placement` | Layout | |-------------|--------| | `docked` (default) | Fixed mini-map bottom-right of the viewport, 280 × 200 px, click to expand to `/_graph`. | | `tabs` | Widget shares the transclusion right rail via a two-tab header. | | `stacked` | Widget sits above the transclusion panel in the right rail. | Switching placement sets a `data-placement` attribute on the shell and flips CSS — the Sigma instance itself is untouched. See [[Configuration]] for the full `theme.toml` reference including `[vendor.*]` pins for the bundled Sigma / graphology packages."},{"h":"Preserving the persistent shell","l":97,"t":"If you rewrite `base.html` from scratch, preserve two markers so the SPA shell keeps working: - Wrap the sidebar and graph widget in `{% block persistent_shell %}` — this region never gets swapped. - Put `data-zetl-volatile` on the element (usually `<main>`) whose `innerHTML` should be replaced on navigation. Omitting them is a valid opt-out: the theme still renders, but the graph re-initialises on every page click."},{"h":"Lifecycle events for custom JS","l":106,"t":"When `[spa].enabled = true`, the shell fires two `window` events around every same-origin navigation: - `zetl:before-navigate` — cancelable; `detail = { fromSlug, toSlug, url }`. - `zetl:after-navigate` — `detail = { slug, contentRoot }`. Use them to re-run Mermaid, KaTeX, or any other enhancement on swapped content:"},{"h":"Distributing a theme","l":123,"t":"Themes are a folder, so they ship as a git repo. `zetl theme install <git-url>` clones one into `.zetl/themes/`, `zetl theme remove <name>` deletes it."},{"h":"Related","l":127,"t":"- [[Configuration]] - [[Web Server]] - [[Static Site Export]] - [[Frontmatter Fields]] - [[Lifecycle Hooks]]"}],"tf":{"200":1,"280":1,"a":17,"above":1,"accept":1,"across":1,"all":1,"an":2,"and":10,"any":3,"are":2,"around":1,"as":2,"assets":1,"at":3,"attribute":1,"back":2,"bargain":1,"be":1,"below":1,"bigger":1,"binary":1,"blocks":1,"both":1,"bottom":1,"built":3,"bundled":2,"but":1,"by":1,"can":1,"cancelable":1,"careful":1,"change":1,"changes":2,"child":1,"class":1,"click":3,"clones":1,"colour":1,"colours":1,"com":1,"compatible":1,"config":1,"configuration":3,"containing":1,"content":1,"contract":1,"creating":1,"css":6,"custom":3,"customising":1,"dark":1,"default":3,"defaults":1,"deletes":1,"deliberately":1,"distributing":1,"dom":1,"don":2,"editing":1,"element":1,"endpoint":1,"enhancement":1,"events":2,"every":4,"everything":1,"expand":1,"export":3,"exposes":2,"fall":1,"falls":1,"fields":1,"file":1,"fires":1,"fixed":1,"flash":1,"flip":1,"flips":1,"folder":2,"for":5,"from":1,"frontmatter":1,"full":2,"gets":1,"git":1,"github":1,"graph":9,"graphology":1,"groups":1,"has":1,"have":1,"header":1,"hooks":1,"html":2,"https":1,"if":1,"in":5,"including":1,"initialises":2,"ins":1,"instance":2,"into":3,"is":9,"it":3,"itself":1,"jinja2":1,"js":2,"katex":1,"keep":1,"keeps":1,"key":1,"layout":3,"least":1,"lifecycle":2,"list":1,"lives":2,"look":1,"makes":1,"map":2,"markers":2,"matter":1,"mermaid":1,"mini":2,"minijinja":3,"missing":1,"mitsuhiko":1,"mode":1,"most":2,"multi":1,"name":1,"navigation":3,"need":1,"neutral":1,"never":1,"no":2,"not":2,"of":4,"omitting":1,"on":7,"one":2,"only":3,"opt":1,"optional":1,"or":3,"origin":1,"other":1,"otherwise":1,"out":1,"override":4,"own":1,"packages":1,"page":4,"panel":1,"persistent":4,"pins":1,"placement":1,"placements":2,"point":2,"possible":1,"preserve":2,"preserving":1,"probably":1,"properties":3,"property":2,"put":1,"px":1,"rail":2,"re":4,"read":1,"reason":1,"rebuild":1,"reducers":1,"reference":1,"refresh":1,"region":1,"related":1,"renders":1,"replaced":1,"repo":1,"restyle":1,"restyling":1,"rewrite":2,"right":3,"run":1,"s":1,"same":1,"scratch":1,"see":1,"server":2,"set":1,"sets":1,"settings":1,"shares":1,"shell":6,"ship":2,"should":1,"sidebar":2,"sigma":4,"single":2,"site":3,"sits":1,"size":1,"so":5,"spa":1,"start":1,"starting":1,"static":3,"still":1,"story":1,"survives":1,"swapped":2,"switched":1,"switching":1,"t":2,"tab":1,"tables":1,"templates":4,"that":5,"the":38,"them":3,"theme":7,"themes":4,"theming":1,"then":1,"they":1,"this":1,"three":1,"to":11,"toggle":1,"top":1,"touching":1,"track":1,"transclusion":2,"two":4,"typography":2,"under":1,"untouched":1,"use":3,"usually":1,"valid":1,"vault":1,"versioned":1,"via":2,"viewport":1,"visual":1,"want":2,"web":2,"what":2,"when":1,"where":1,"whose":1,"why":1,"widget":5,"will":1,"without":1,"working":1,"works":1,"wrap":1,"you":7,"your":1,"zetl":2}},{"dl":425,"n":"Static Site Export","s":"reading/static-site-export","secs":[{"h":"Static Site Export","l":6,"t":"`zetl build` turns your vault into a folder of plain HTML, CSS, and JavaScript. No runtime, no database, no server process — every page is a file you can upload to any static host. The output looks the same as [[Web Server]] minus the editor."},{"h":"Why build a static site","l":10,"t":"A static site is the right deliverable when: - You want to publish research notes, a personal wiki, or project docs to the public internet. - You want a vault readable from any browser with zero operational overhead. - You want something to hand to collaborators, reviewers, or your future self that doesn't depend on the `zetl` binary being installed. - You want archival: HTML works the same way in 2040 as it does in 2026. Because nothing on the published site writes back, the edit button is absent and the save/delete endpoints aren't emitted. Everything else — wikilinks, backlinks, transclusion panels with SVG bridges, the graph widget, full-text search, per-page history (if built with `--features history`) — lands in the output as static HTML and JSON."},{"h":"Running it","l":21,"t":"Preview the result locally with any static file server: Then open `http://localhost:8080/`."},{"h":"Output structure","l":39,"t":"Each note lives at a stable URL (`/page/<slug>/`) driven by the page title, so `[[Zettelkasten Method]]` in source becomes `<a href=\"/page/Zettelkasten Method/\">` in HTML. Slugs survive across builds as long as the page title doesn't change."},{"h":"Flags that matter","l":57,"t":"| Flag | Default | Purpose | |------|---------|---------| | `-o, --out-dir <DIR>` | `dist` | Output directory. Wiped and rewritten on each build. | | `--theme <THEME>` | `default` | Theme from `.zetl/themes/<name>/`. See [[Customising the Look]]. | | `--public <DIR>` | — | Files copied over the output root after generation (good for `CNAME`, `robots.txt`). | | `--site-url <URL>` | — | Canonical URL; used for absolute `og:image` URLs so social scrapers resolve them. | | `--safe-mode` | off | Skip every hook except ones the theme explicitly declares. | | `--at <TIME-EXPR>` | — | Build a past snapshot. See [[Time Travel]]. | | `--strict-parsers` | off | Promote mixed-parser warnings to errors. |"},{"h":"Hosting","l":69,"t":"`dist/` is a plain folder. Any of these work: - **GitHub Pages / Codeberg Pages** — commit `dist/` to a pages branch, or emit straight into your pages repo. - **Netlify / Cloudflare Pages / Vercel** — point at the repo, set the build command to `zetl build`, publish directory `dist`. - **S3 + CloudFront, Backblaze B2, any object store** — `aws s3 sync dist/ s3://bucket/` and serve via the usual static-site front. - **Your own server** — `rsync -a --delete dist/ user@host:/var/www/vault/` with nginx or Caddy in front. There is no runtime for any of these to support — the graph widget, search, and SPA navigation shell are all client-side JS bundled under `_static/`. No CDN fallback, no analytics pings."},{"h":"Building vs serving","l":80,"t":"Rule of thumb: use [[Web Server]] while you're writing and linking; use `zetl build` when you want to publish. Many workflows run both — `zetl serve` during authoring, `zetl build` from CI on every push to main."},{"h":"Related","l":84,"t":"- [[Web Server]] - [[Customising the Look]] - [[Capability URLs]] - [[Time Travel]] - [[CLI Overview]]"}],"tf":{"2026":1,"2040":1,"a":10,"absent":1,"absolute":1,"across":1,"after":1,"all":1,"analytics":1,"and":7,"any":6,"archival":1,"are":1,"aren":1,"as":5,"at":2,"authoring":1,"b2":1,"back":1,"backblaze":1,"backlinks":1,"because":1,"becomes":1,"being":1,"binary":1,"both":1,"branch":1,"bridges":1,"browser":1,"build":4,"building":1,"builds":1,"built":1,"bundled":1,"button":1,"by":1,"caddy":1,"can":1,"canonical":1,"capability":1,"cdn":1,"change":1,"ci":1,"cli":1,"client":1,"cloudflare":1,"cloudfront":1,"codeberg":1,"collaborators":1,"command":1,"commit":1,"copied":1,"css":1,"customising":2,"database":1,"declares":1,"default":1,"delete":1,"deliverable":1,"depend":1,"directory":2,"docs":1,"does":1,"doesn":2,"driven":1,"during":1,"each":2,"edit":1,"editor":1,"else":1,"emit":1,"emitted":1,"endpoints":1,"errors":1,"every":3,"everything":1,"except":1,"explicitly":1,"export":1,"fallback":1,"file":2,"files":1,"flag":1,"flags":1,"folder":2,"for":3,"from":3,"front":2,"full":1,"future":1,"generation":1,"github":1,"good":1,"graph":2,"hand":1,"history":1,"hook":1,"host":1,"hosting":1,"html":4,"if":1,"in":6,"installed":1,"internet":1,"into":2,"is":5,"it":2,"javascript":1,"js":1,"json":1,"lands":1,"linking":1,"lives":1,"locally":1,"long":1,"look":2,"looks":1,"main":1,"many":1,"matter":1,"minus":1,"mixed":1,"navigation":1,"netlify":1,"nginx":1,"no":6,"note":1,"notes":1,"nothing":1,"object":1,"of":4,"off":2,"on":4,"ones":1,"open":1,"operational":1,"or":4,"output":5,"over":1,"overhead":1,"overview":1,"own":1,"page":4,"pages":5,"panels":1,"parser":1,"past":1,"per":1,"personal":1,"pings":1,"plain":2,"point":1,"preview":1,"process":1,"project":1,"promote":1,"public":1,"publish":3,"published":1,"purpose":1,"push":1,"re":1,"readable":1,"related":1,"repo":2,"research":1,"resolve":1,"result":1,"reviewers":1,"rewritten":1,"right":1,"root":1,"rule":1,"run":1,"running":1,"runtime":2,"s3":1,"same":2,"save":1,"scrapers":1,"search":2,"see":2,"self":1,"serve":1,"server":6,"serving":1,"set":1,"shell":1,"side":1,"site":5,"skip":1,"slugs":1,"snapshot":1,"so":2,"social":1,"something":1,"source":1,"spa":1,"stable":1,"static":7,"store":1,"straight":1,"structure":1,"support":1,"survive":1,"svg":1,"t":3,"text":1,"that":2,"the":23,"them":1,"theme":2,"then":1,"there":1,"these":2,"thumb":1,"time":2,"title":2,"to":11,"transclusion":1,"travel":2,"turns":1,"under":1,"upload":1,"url":2,"urls":2,"use":2,"used":1,"usual":1,"vault":2,"vercel":1,"via":1,"vs":1,"want":5,"warnings":1,"way":1,"web":3,"when":2,"while":1,"why":1,"widget":2,"wiki":1,"wikilinks":1,"wiped":1,"with":5,"work":1,"workflows":1,"works":1,"writes":1,"writing":1,"you":7,"your":4,"zero":1}},{"dl":478,"n":"Web Server","s":"reading/web-server","secs":[{"h":"Web Server","l":6,"t":"`zetl serve` starts a local web server that renders your vault as a browsable site. You get rendered Markdown, working wikilinks, a live backlink panel, a persistent graph mini-map, and an in-browser editor. It's the fastest way to actually read — and edit — a vault of more than a handful of notes."},{"h":"Why run a server","l":10,"t":"A static export ([[Static Site Export]]) shows your vault as it was when you last built it. A terminal viewer ([[Terminal Viewer]]) shows one note at a time. `zetl serve` is what you want between those: a live view of the vault *right now*, with: - **Rendered pages** — Markdown, wikilinks, embeds, and code blocks, rendered through Minijinja templates with YAML frontmatter in scope. - **Backlinks panel** — every page that points here, updated on every save. - **Transclusion panel** — forward-link excerpts as cards in the right rail, connected to anchors in the main text by SVG bridges (the same Xanadu idea as the terminal viewer). - **Live search** — full-text search across the vault from a modal. - **CodeMirror editor** — click Edit, change the note, hit Save. The index rebuilds on save; no reload dance. A delete button is a click away. - **Persistent graph mini-map** — a Sigma.js WebGL graph of your vault, docked bottom-right, that survives page navigation (no flash, no re-layout). - **History strip** — when zetl was built with `--features history`, every page shows when it last changed and links to its timeline. See [[Page History]]."},{"h":"Running it","l":22,"t":"Open `http://localhost:3000/` to land on the vault index. Wikilinks in rendered pages are clickable. `Ctrl-P` (or whatever the active theme binds) opens the search modal."},{"h":"Flags that matter","l":33,"t":"| Flag | Default | Purpose | |------|---------|---------| | `-p, --port <PORT>` | `3000` | TCP port. | | `--theme <THEME>` | `default` | Theme from `.zetl/themes/<name>/`. See [[Customising the Look]]. | | `--public <DIR>` | — | Files in this directory override generated pages (good for `robots.txt`, custom error pages). | | `--collab` | off | Switches to multi-user mode with passkeys and CRDT sync. See [[Running a Team Server]]. | | `--safe-mode` | off | Skip every hook except ones declared in the theme. | | `--at <TIME-EXPR>` | — | Serve a past snapshot. See [[Time Travel]]. | | `--exclude <PATTERN>` | — | Gitignore-syntax exclusion, repeatable. |"},{"h":"Single-user vs collab","l":45,"t":"Out of the box, `zetl serve` is single-user and trusts whoever reaches the port. There's no login, no accounts; editing works immediately. This is the right mode when the server is on your laptop or behind a VPN. With `--collab`, zetl turns into a multi-user server: WebAuthn passkey login, real-time CRDT editing over WebSockets, access control, invitations. That whole stack — bootstrapping an owner, inviting collaborators, deterministic server keys — lives in [[Running a Team Server]]."},{"h":"API endpoints","l":51,"t":"`serve` exposes a small JSON API on the same port. A brief sample: | Endpoint | Method | Purpose | |----------|--------|---------| | `/api/pages` | GET | List every page | | `/api/pages/{slug}` | GET/PUT/DELETE | Read, update, remove | | `/api/search?q=...` | GET | Full-text search | | `/api/graph` | GET | Full link graph JSON | | `/api/index` | POST | Trigger a reindex | See [[CLI Overview]] for the full endpoint list including the collab-only WebSocket and access-request endpoints."},{"h":"Related","l":65,"t":"- [[Static Site Export]] - [[Terminal Viewer]] - [[Customising the Look]] - [[Running a Team Server]] - [[CLI Overview]]"}],"tf":{"a":24,"access":2,"accounts":1,"across":1,"active":1,"actually":1,"an":2,"anchors":1,"and":7,"api":2,"are":1,"as":4,"at":1,"away":1,"backlink":1,"backlinks":1,"behind":1,"between":1,"binds":1,"blocks":1,"bootstrapping":1,"bottom":1,"box":1,"bridges":1,"brief":1,"browsable":1,"browser":1,"built":2,"button":1,"by":1,"cards":1,"change":1,"changed":1,"cli":2,"click":2,"clickable":1,"code":1,"codemirror":1,"collab":2,"collaborators":1,"connected":1,"control":1,"crdt":2,"custom":1,"customising":2,"dance":1,"declared":1,"default":1,"delete":2,"deterministic":1,"directory":1,"docked":1,"edit":2,"editing":2,"editor":2,"embeds":1,"endpoint":2,"endpoints":2,"error":1,"every":5,"except":1,"excerpts":1,"exclusion":1,"export":3,"exposes":1,"fastest":1,"files":1,"flag":1,"flags":1,"flash":1,"for":2,"forward":1,"from":2,"frontmatter":1,"full":4,"generated":1,"get":5,"gitignore":1,"good":1,"graph":4,"handful":1,"here":1,"history":2,"hit":1,"hook":1,"idea":1,"immediately":1,"in":8,"including":1,"index":2,"into":1,"invitations":1,"inviting":1,"is":5,"it":5,"its":1,"js":1,"json":2,"keys":1,"land":1,"laptop":1,"last":2,"layout":1,"link":2,"links":1,"list":2,"live":3,"lives":1,"local":1,"login":2,"look":2,"main":1,"map":2,"markdown":2,"matter":1,"method":1,"mini":2,"minijinja":1,"modal":2,"mode":2,"more":1,"multi":2,"navigation":1,"no":5,"note":2,"notes":1,"now":1,"of":5,"off":2,"on":5,"one":1,"ones":1,"only":1,"open":1,"opens":1,"or":2,"out":1,"over":1,"override":1,"overview":2,"owner":1,"page":5,"pages":4,"panel":3,"passkey":1,"passkeys":1,"past":1,"persistent":2,"points":1,"port":3,"post":1,"purpose":2,"put":1,"rail":1,"re":1,"reaches":1,"read":2,"real":1,"rebuilds":1,"reindex":1,"related":1,"reload":1,"remove":1,"rendered":4,"renders":1,"repeatable":1,"request":1,"right":4,"run":1,"running":4,"s":2,"same":2,"sample":1,"save":3,"scope":1,"search":4,"see":5,"serve":1,"server":9,"shows":3,"sigma":1,"single":2,"site":3,"skip":1,"small":1,"snapshot":1,"stack":1,"starts":1,"static":3,"strip":1,"survives":1,"svg":1,"switches":1,"sync":1,"syntax":1,"tcp":1,"team":3,"templates":1,"terminal":4,"text":3,"than":1,"that":5,"the":22,"theme":3,"there":1,"this":2,"those":1,"through":1,"time":3,"timeline":1,"to":5,"transclusion":1,"travel":1,"trigger":1,"trusts":1,"turns":1,"update":1,"updated":1,"user":4,"vault":7,"view":1,"viewer":4,"vpn":1,"vs":1,"want":1,"was":2,"way":1,"web":2,"webauthn":1,"webgl":1,"websocket":1,"websockets":1,"what":1,"whatever":1,"when":4,"whoever":1,"whole":1,"why":1,"wikilinks":3,"with":5,"working":1,"works":1,"xanadu":1,"yaml":1,"you":3,"your":4,"zetl":2}},{"dl":450,"n":"Terminal Viewer","s":"reading/terminal-viewer","secs":[{"h":"Terminal Viewer","l":6,"t":"`zetl view` is a Xanadu-inspired two-pane reader for reading linked notes without leaving the terminal. It exists because most of the time you don't want a browser — you want to sit with one note, glance at what it points to, and jump onwards when something catches your eye."},{"h":"Why two panes","l":10,"t":"When you read a note in a normal editor, forward-links are just names — `[[Zettelkasten Method]]` tells you nothing about what Zettelkasten Method actually *says*. Ted Nelson's original vision for Xanadu put the cited text right next to the citing text, bridged by a visible connector. `zetl view` does exactly that in a TTY: - **Main pane** (left) — the note you asked for, rendered as Markdown. Every wikilink is annotated with a numbered anchor glyph `[1]`, `[2]`, `[3]` in reading order. - **Context column** (right) — a stack of context cards, one per forward link. Each card shows the first few lines of the linked note, coloured to match its anchor. - **Bridge column** (middle) — coloured connectors from each `[N]` anchor to its card, so your eye can follow the citation at a glance. In terminals narrower than 60 columns the layout collapses to single-pane and context cards become inline footnotes. You lose the bridges but keep the anchors."},{"h":"Opening a page","l":20,"t":"With no argument, `zetl view` launches an interactive picker of every page in the vault — type to filter, Enter to open."},{"h":"Flags","l":31,"t":"| Flag | Default | Purpose | |------|---------|---------| | `--context-lines <N>` | `5` | Lines shown per context card. Range 1–20. | | `--main-width <pct>` | `58` | Percent of terminal columns for the main pane. Range 30–80. | | `--at <TIME-EXPR>` | — | Read a past snapshot (see [[Time Travel]]). | | `-d, --dir <DIR>` | `.` | Vault root. |"},{"h":"Keybindings","l":40,"t":"| Key | Action | |-----|--------| | `j` / `k` | Scroll down / up (or cycle anchors in focus mode) | | `Ctrl-d` / `Ctrl-u` | Half-page down / up | | `g` / `G` | Jump to top / bottom of the note | | `Tab` | Toggle between scroll mode and focus mode | | `Enter` | Navigate to the focused link | | `[` / `]` | Session history: back / forward | | `/` | Open the page picker | | `?` | Toggle the keybindings overlay | | `q` | Quit | **Focus mode** is what makes the navigation fast. Hit `Tab`, then `j`/`k` steps from one anchor to the next (not one line at a time); the bridge column highlights the selected anchor's connector; `Enter` opens that page. `[` returns to where you came from, so you can explore breadth-first through a chain of notes and back-track without losing your place."},{"h":"When to reach for it","l":56,"t":"Use `zetl view` when you're: - Thinking through a research question where the notes form a web and you want citations visible without tab-switching. - Proofreading a chain of linked notes on a server over SSH where a browser isn't available. - Navigating a vault whose backlinks and forward-links you don't know by heart yet. For heavier reading — embedded images, wide tables, the full graph view — reach for [[Web Server]] instead."},{"h":"Related","l":66,"t":"- [[Web Server]] - [[Following Links]] - [[Wikilinks]] - [[Time Travel]] - [[CLI Overview]]"}],"tf":{"1":1,"20":1,"30":1,"60":1,"80":1,"a":19,"about":1,"action":1,"actually":1,"an":1,"anchor":5,"anchors":2,"and":6,"annotated":1,"are":1,"argument":1,"as":1,"asked":1,"at":3,"available":1,"back":2,"backlinks":1,"because":1,"become":1,"between":1,"bottom":1,"breadth":1,"bridge":2,"bridged":1,"bridges":1,"browser":2,"but":1,"by":2,"came":1,"can":2,"card":3,"cards":2,"catches":1,"chain":2,"citation":1,"citations":1,"cited":1,"citing":1,"cli":1,"collapses":1,"coloured":2,"column":3,"columns":2,"connector":2,"connectors":1,"context":4,"cycle":1,"default":1,"does":1,"don":2,"down":2,"each":2,"editor":1,"embedded":1,"enter":1,"every":2,"exactly":1,"exists":1,"explore":1,"eye":2,"fast":1,"few":1,"filter":1,"first":2,"flag":1,"flags":1,"focus":3,"focused":1,"follow":1,"following":1,"footnotes":1,"for":7,"form":1,"forward":4,"from":3,"full":1,"glance":2,"glyph":1,"graph":1,"half":1,"heart":1,"heavier":1,"highlights":1,"history":1,"hit":1,"images":1,"in":6,"inline":1,"inspired":1,"instead":1,"interactive":1,"is":3,"isn":1,"it":3,"its":2,"jump":2,"just":1,"keep":1,"key":1,"keybindings":2,"know":1,"launches":1,"layout":1,"leaving":1,"left":1,"line":1,"lines":2,"link":2,"linked":3,"links":3,"lose":1,"losing":1,"main":2,"makes":1,"markdown":1,"match":1,"method":1,"middle":1,"mode":4,"most":1,"names":1,"narrower":1,"navigate":1,"navigating":1,"navigation":1,"nelson":1,"next":2,"no":1,"normal":1,"not":1,"note":5,"notes":4,"nothing":1,"numbered":1,"of":8,"on":1,"one":4,"onwards":1,"open":2,"opening":1,"opens":1,"or":1,"order":1,"original":1,"over":1,"overlay":1,"overview":1,"page":5,"pane":4,"panes":1,"past":1,"per":2,"percent":1,"picker":2,"place":1,"points":1,"proofreading":1,"purpose":1,"put":1,"question":1,"quit":1,"range":2,"re":1,"reach":2,"read":2,"reader":1,"reading":3,"related":1,"rendered":1,"research":1,"returns":1,"right":2,"root":1,"s":2,"says":1,"scroll":2,"see":1,"selected":1,"server":3,"session":1,"shown":1,"shows":1,"single":1,"sit":1,"snapshot":1,"so":2,"something":1,"ssh":1,"stack":1,"steps":1,"switching":1,"t":3,"tab":1,"tables":1,"ted":1,"tells":1,"terminal":3,"terminals":1,"text":2,"than":1,"that":2,"the":23,"then":1,"thinking":1,"through":2,"time":4,"to":13,"toggle":2,"top":1,"track":1,"travel":2,"tty":1,"two":2,"type":1,"up":2,"use":1,"vault":3,"view":1,"viewer":1,"visible":2,"vision":1,"want":3,"web":3,"what":3,"when":4,"where":3,"whose":1,"why":1,"wide":1,"wikilink":1,"wikilinks":1,"with":3,"without":3,"xanadu":2,"yet":1,"you":11,"your":3,"zettelkasten":1}},{"dl":526,"n":"Invitations","s":"collaboration/invitations","secs":[{"h":"Invitations","l":6,"t":"New collaborators join a vault via a single-use, signed **invitation token**. You generate one with `zetl invite` (or the web UI), send the resulting link by whatever out-of-band channel you prefer, and the recipient uses it exactly once to register a passkey and pick a display name."},{"h":"Generating an invite from the CLI","l":10,"t":"The minimal form takes your own username (the inviter) and the role the invitee will have: This creates the token and copies the full invitation URL to the clipboard. Paste it into Signal, email, a sticky note — whatever gets it to the recipient without a third party logging it along the way. Three roles are available: | Role | What they can do | |------|------------------| | `reader` | View pages; no editing, no invitations. | | `editor` | View and edit pages; cannot invite new users or change access. | | `admin` | Full control, including issuing invitations and managing passkeys. |"},{"h":"Scoping an invite to specific pages","l":28,"t":"By default an invite gives the recipient access to the whole vault at their role. For a contractor who should only touch one project, or a reviewer who only needs to see a single folder, use `--pages` with a glob: Globs match on the vault-relative path. `projects/*` matches direct children; `projects/**` matches everything underneath, recursively. For more expressive rules — \"editor on everything *except* `billing/**`\", or \"editor only between 9–5\" — see SPL-based deontic rules in [[Access Control]]."},{"h":"Custom expiry","l":42,"t":"Invitation links expire. The default is 72 hours, which is long enough for someone to find the email but short enough that a forgotten link doesn't become a permanent back door: Accepted units are `h` (hours) and `d` (days). After expiry the token refuses to redeem even if it hasn't been used."},{"h":"Using the web UI instead","l":56,"t":"If the CLI isn't handy, `/_admin/invite` does the same thing with a form. Admins can also see a list of outstanding invites, revoke un-redeemed ones, and check which tokens have been used. This is the easier surface for day-to-day invitations; the CLI is better for scripting and one-liners."},{"h":"What's in the token","l":60,"t":"Each invite is an Ed25519-signed blob containing the role, optional page scope, expiry, and a single-use nonce. The server refuses to redeem a token whose nonce it has already seen, so a link can't be replayed even if someone intercepts it. The server key that signs these tokens is the same key derived from your `--server-key-seed` mnemonic — see [[Passkeys and Accounts]]. If you rotate the seed, all outstanding invites become invalid."},{"h":"Accepting an invite","l":66,"t":"The recipient opens the link, enters a display name, registers a passkey, and is dropped into the vault at their scoped role. They don't need their own 12-word phrase at this point — zetl generates one and shows it to them during onboarding, to save for recovery. (They should save it. See [[Passkeys and Accounts]].)"},{"h":"Revoking access","l":70,"t":"To remove a collaborator entirely, an admin deletes the user from `/_admin/users`. To narrow scope, issue a new, more restrictive invite and revoke the old role. There's no separate \"suspend\" state in v0.5; access is either on or off."},{"h":"Related","l":74,"t":"- [[Running a Team Server]] - [[Passkeys and Accounts]] - [[Access Control]] - [[Co-editing]] - [[Capability URLs]]"}],"tf":{"12":1,"5":2,"72":1,"9":1,"a":22,"accepted":1,"accepting":1,"access":6,"accounts":3,"admin":1,"admins":1,"after":1,"all":1,"along":1,"already":1,"also":1,"an":6,"and":16,"are":2,"at":3,"available":1,"back":1,"band":1,"based":1,"be":1,"become":2,"been":2,"better":1,"between":1,"blob":1,"but":1,"by":2,"can":3,"cannot":1,"capability":1,"change":1,"channel":1,"check":1,"children":1,"cli":3,"clipboard":1,"co":1,"collaborator":1,"collaborators":1,"containing":1,"contractor":1,"control":3,"copies":1,"creates":1,"custom":1,"day":2,"days":1,"default":2,"deletes":1,"deontic":1,"derived":1,"direct":1,"display":2,"do":1,"does":1,"doesn":1,"don":1,"door":1,"dropped":1,"during":1,"each":1,"easier":1,"ed25519":1,"edit":1,"editing":2,"editor":2,"either":1,"email":2,"enough":2,"enters":1,"entirely":1,"even":2,"everything":2,"exactly":1,"except":1,"expire":1,"expiry":3,"expressive":1,"find":1,"folder":1,"for":6,"forgotten":1,"form":2,"from":3,"full":2,"generate":1,"generates":1,"generating":1,"gets":1,"gives":1,"glob":1,"globs":1,"handy":1,"has":1,"hasn":1,"have":2,"hours":2,"if":4,"in":3,"including":1,"instead":1,"intercepts":1,"into":2,"invalid":1,"invitation":3,"invitations":4,"invite":7,"invitee":1,"inviter":1,"invites":2,"is":8,"isn":1,"issue":1,"issuing":1,"it":9,"join":1,"key":2,"liners":1,"link":4,"links":1,"list":1,"logging":1,"long":1,"managing":1,"match":1,"matches":2,"minimal":1,"mnemonic":1,"more":2,"name":2,"narrow":1,"need":1,"needs":1,"new":3,"no":3,"nonce":2,"note":1,"of":2,"off":1,"old":1,"on":3,"onboarding":1,"once":1,"one":4,"ones":1,"only":3,"opens":1,"optional":1,"or":5,"out":1,"outstanding":2,"own":2,"page":1,"pages":3,"party":1,"passkey":2,"passkeys":4,"paste":1,"path":1,"permanent":1,"phrase":1,"pick":1,"point":1,"prefer":1,"project":1,"recipient":4,"recovery":1,"recursively":1,"redeem":2,"redeemed":1,"refuses":2,"register":1,"registers":1,"related":1,"relative":1,"remove":1,"replayed":1,"restrictive":1,"resulting":1,"reviewer":1,"revoke":2,"revoking":1,"role":6,"roles":1,"rotate":1,"rules":2,"running":1,"s":2,"same":2,"save":2,"scope":2,"scoped":1,"scoping":1,"scripting":1,"see":5,"seed":1,"seen":1,"send":1,"separate":1,"server":3,"short":1,"should":2,"shows":1,"signal":1,"signed":2,"signs":1,"single":3,"so":1,"someone":2,"specific":1,"spl":1,"state":1,"sticky":1,"surface":1,"suspend":1,"t":5,"takes":1,"team":1,"that":2,"the":35,"their":3,"them":1,"there":1,"these":1,"they":3,"thing":1,"third":1,"this":3,"three":1,"to":14,"token":5,"tokens":2,"touch":1,"ui":2,"un":1,"underneath":1,"units":1,"url":1,"urls":1,"use":3,"used":2,"user":1,"username":1,"users":1,"uses":1,"using":1,"v0":1,"vault":4,"via":1,"view":2,"way":1,"web":2,"what":2,"whatever":2,"which":2,"who":2,"whole":1,"whose":1,"will":1,"with":3,"without":1,"word":1,"you":3,"your":2,"zetl":1}},{"dl":583,"n":"Access Control","s":"collaboration/access-control","secs":[{"h":"Access Control","l":6,"t":"zetl's access model has two layers. The built-in layer — **three roles plus glob-based page scoping** — covers the 90% case: readers, editors, admins, optionally confined to a folder. The optional layer — **SPL-based deontic rules** — lets you express richer policies (\"editor everywhere except `billing/**`\", \"read access only between 9:00 and 17:00\") using the same reasoning engine that powers [[What is defeasible reasoning]]."},{"h":"The three roles","l":10,"t":"| Role | View | Edit | Invite | Admin UI | |------|:----:|:----:|:------:|:--------:| | `reader` | yes | no | no | no | | `editor` | yes | yes | no | no | | `admin` | yes | yes | yes | yes | A user's role is set on the invitation that brought them in (see [[Invitations]]) and can be changed later from `/_admin/users`. There's no implicit hierarchy beyond this table — an editor isn't a superset of admin, they're just different."},{"h":"Page scoping with globs","l":20,"t":"When you issue an invitation you can restrict which pages the recipient reaches at all: The scope is a glob against vault-relative paths. `projects/*` matches direct children of `projects/`. `projects/**` matches everything recursively. You can pass only one `--pages` at invite time; for more intricate patterns, use deontic rules below. Pages outside the scope are invisible — not just uneditable. An out-of-scope page won't appear in search, won't show up in the graph, and returns 404 if requested by URL. This is a deliberate minimum-surface choice: users don't learn about documents they can't touch."},{"h":"Deontic rules with SPL","l":32,"t":"> **Requires `--features reason` at install time.** See [[Installation]]. Glob-based scoping can't express subtraction (\"editor on `research/**` *except* `research/private/**`\") or time windows. For those, zetl reads deontic rules from SPL blocks in your vault. SPL expresses permission and prohibition with **modal operators** — `(may X)`, `(must X)`, `(forbidden X)` — used as the *head* of a `normally` rule: Deontic rules compose with defeasible reasoning: a narrower forbidding rule can **defeat** a broader permitting one via `(prefer editors-forbidden-billing editor-may-edit-research)`. You write policy as overlapping layers rather than as a flat table. Conflicts show up in `zetl reason conflicts` just like any other defeasible conflict. See [[Writing SPL]] for the full syntax, [[Running Queries]] for how to test rules, and the upstream [Spindle SPL reference](https://codeberg.org/anuna/spindle-rust) for the complete modal-operator specification. Time-bounded access is typically encoded via temporal literals or a fact that an external hook asserts at the appropriate time:"},{"h":"The `on-access-request` hook","l":66,"t":"When a user tries to do something zetl's built-in rules forbid — view a scoped-out page, edit a read-only one — zetl can run an `on-access-request` hook instead of silently denying. The hook receives the user, the requested action, the target page, and the justification (a free-text field the user filled in) on stdin as JSON. It prints a decision — `allow`, `deny`, or `defer` — and zetl acts on that. This is how you wire in a custom approval flow without forking zetl: - **Slack approval bot**: the hook posts to a channel and waits for a thumbs-up. - **Auto-approve inside working hours, page an admin otherwise**. - **Deny but email the admin** for an audit trail. The hook is a normal zetl lifecycle hook — any executable, any language. See [[Lifecycle Hooks]] for the hook runtime and payload conventions."},{"h":"Keeping it grounded","l":78,"t":"For a small team vault, three roles plus `--pages` glob scoping is almost always enough. Reach for SPL deontic rules when you have a real policy — HR-sensitive folders, time-bounded contractors, a reviewer who should see A and B but not C — not as a matter of architectural tidiness. Plain roles leave less room to get wrong."},{"h":"Related","l":82,"t":"- [[Invitations]] - [[Running a Team Server]] - [[Lifecycle Hooks]] - [[Writing SPL]] - [[What is SPL]]"}],"tf":{"00":2,"17":1,"404":1,"9":1,"90":1,"a":25,"about":1,"access":4,"action":1,"acts":1,"admin":4,"admins":1,"against":1,"all":1,"almost":1,"always":1,"an":7,"and":10,"anuna":1,"any":3,"appear":1,"appropriate":1,"approval":2,"approve":1,"architectural":1,"are":1,"as":5,"asserts":1,"at":4,"audit":1,"auto":1,"b":1,"based":3,"be":1,"below":1,"between":1,"beyond":1,"blocks":1,"bot":1,"bounded":2,"broader":1,"brought":1,"built":2,"but":2,"by":1,"c":1,"can":7,"case":1,"changed":1,"channel":1,"children":1,"choice":1,"codeberg":1,"complete":1,"compose":1,"confined":1,"conflict":1,"conflicts":1,"contractors":1,"control":1,"conventions":1,"covers":1,"custom":1,"decision":1,"defeasible":3,"defeat":1,"deliberate":1,"deny":1,"denying":1,"deontic":6,"different":1,"direct":1,"do":1,"documents":1,"don":1,"edit":2,"editor":3,"editors":1,"email":1,"encoded":1,"engine":1,"enough":1,"everything":1,"everywhere":1,"except":2,"executable":1,"express":2,"expresses":1,"external":1,"fact":1,"field":1,"filled":1,"flat":1,"flow":1,"folder":1,"folders":1,"for":10,"forbid":1,"forbidding":1,"forking":1,"free":1,"from":2,"full":1,"get":1,"glob":4,"globs":1,"graph":1,"grounded":1,"has":1,"have":1,"head":1,"hierarchy":1,"hook":8,"hooks":2,"hours":1,"how":2,"hr":1,"https":1,"if":1,"implicit":1,"in":9,"inside":1,"install":1,"installation":1,"instead":1,"intricate":1,"invisible":1,"invitation":2,"invitations":2,"invite":2,"is":9,"isn":1,"issue":1,"it":2,"json":1,"just":3,"justification":1,"keeping":1,"language":1,"later":1,"layer":2,"layers":2,"learn":1,"leave":1,"less":1,"lets":1,"lifecycle":3,"like":1,"literals":1,"matches":2,"matter":1,"minimum":1,"modal":2,"model":1,"more":1,"narrower":1,"no":6,"normal":1,"not":3,"of":6,"on":4,"one":3,"only":3,"operator":1,"operators":1,"optional":1,"optionally":1,"or":3,"org":1,"other":1,"otherwise":1,"out":2,"outside":1,"overlapping":1,"page":6,"pages":2,"pass":1,"paths":1,"patterns":1,"payload":1,"permission":1,"permitting":1,"plain":1,"plus":2,"policies":1,"policy":2,"posts":1,"powers":1,"prints":1,"prohibition":1,"queries":1,"rather":1,"re":1,"reach":1,"reaches":1,"read":2,"readers":1,"reads":1,"real":1,"reasoning":3,"receives":1,"recipient":1,"recursively":1,"reference":1,"related":1,"relative":1,"requested":2,"requires":1,"restrict":1,"returns":1,"reviewer":1,"richer":1,"role":2,"roles":4,"room":1,"rule":2,"rules":8,"run":1,"running":2,"runtime":1,"rust":1,"s":4,"same":1,"scope":3,"scoped":1,"scoping":4,"search":1,"see":5,"sensitive":1,"server":1,"set":1,"should":1,"show":2,"silently":1,"slack":1,"small":1,"something":1,"specification":1,"spindle":2,"spl":9,"stdin":1,"subtraction":1,"superset":1,"surface":1,"syntax":1,"t":6,"table":2,"target":1,"team":2,"temporal":1,"test":1,"text":1,"than":1,"that":4,"the":26,"them":1,"there":1,"they":2,"this":3,"those":1,"three":3,"thumbs":1,"tidiness":1,"time":6,"to":5,"touch":1,"trail":1,"tries":1,"two":1,"typically":1,"ui":1,"uneditable":1,"up":3,"upstream":1,"url":1,"use":1,"used":1,"user":4,"users":1,"using":1,"vault":3,"via":2,"view":2,"waits":1,"what":2,"when":3,"which":1,"who":1,"windows":1,"wire":1,"with":4,"without":1,"won":2,"working":1,"write":1,"writing":2,"wrong":1,"yes":7,"you":7,"your":1,"zetl":7}},{"dl":671,"n":"Co-editing","s":"collaboration/co-editing","secs":[{"h":"Co-editing","l":6,"t":"When two people open the same page in a `--collab` vault, their edits merge without conflicts. Under the hood zetl uses a **Peritext CRDT** engine over WebSocket: every keystroke is a small op that both clients apply in the same causal order, so the document converges no matter who's typing where. Think Google Docs, but local-first and committed to git."},{"h":"What you see as a user","l":10,"t":"Open a page in the web UI. If someone else is editing it, you see their cursor as a coloured caret with their display name floating above it. Your edits and theirs interleave in real time. There's no \"save\" button — everything is continuously streamed to the server. A small idle period (a few seconds of quiet, or navigating away) triggers an **auto-commit**: zetl writes the page to disk and runs `git commit` with the author set to the person whose session made the last edits. If Alice and Bob were both editing, the commit message notes both contributors. This means your vault's git log is an authentic editing history. You can `git blame` a line and find out who wrote it. You can revert a page with `git revert`. The CRDT layer is an editing affordance; the source of truth is still Markdown on disk, tracked by git."},{"h":"Crash recovery","l":18,"t":"Between auto-commits, in-flight edits live in a **write-ahead log** at `.zetl/collab/wal/`. If the server dies mid-edit — process killed, power cut, container restarted — the WAL is replayed on next startup so nothing is lost. You shouldn't ever need to think about this, but it's why `zetl serve --collab` can take an extra second to come up after a hard crash. If you deploy in an ephemeral container where `.zetl/collab/wal/` vanishes on redeploy, your CRDT merge state is gone. Auto-commit frequency is your backstop: most \"in-flight\" edits are short-lived, and anything older than a few seconds is in git already. For long-lived containers you can persist the WAL directory on a volume."},{"h":"External edits still work","l":24,"t":"The server polls `git` every 30 seconds (tunable via `--git-poll-interval` on `serve`) for commits that landed outside the UI — a `git pull` in a terminal, a push from another machine, a hook that rewrote a page. Those changes show up in the editor without anyone refreshing. If you're actively editing when an external change lands on the same page, zetl merges at the CRDT layer just as it would for a second live user."},{"h":"Authorship and git","l":28,"t":"Every auto-commit carries the editor's display name and the email registered with their account. If Alice edits `Zettelkasten Method.md`, the commit shows: If multiple editors contributed between commits, `git log --format=\"%b\"` will list them. This is plain git — every tool that knows how to read a git repo reads this correctly, including third-party hosts like Forgejo, GitLab, or GitHub."},{"h":"Hooks that fire on save","l":39,"t":"`on-save` lifecycle hooks run every time a page is committed by the collab server. This is how you wire the editor to external workflows: ping a Matrix channel when a project plan changes, regenerate a static site on a CDN, re-run your defeasible-reasoning build, sync to another store. The hook receives the vault path, the page slug, the committing author, and the old/new SHAs on stdin as JSON. See [[Lifecycle Hooks]] for the full set of events and the payload schema."},{"h":"Limits in v0.5","l":45,"t":"A few things the co-editing engine doesn't do yet: - **Selective undo** — the undo stack is local per-client; you can't undo someone else's last edit without editing around it. - **Rich media comments** — inline comments are plain text, tied to a block by its content hash. See [[Blocks]] for how zetl addresses paragraphs. - **Offline with later sync** — if your browser disconnects, your local edits pause. They'll send on reconnect, but if the server has moved on, you may see a merge nudge. For true offline work, edit on disk and let the git-poll loop pick it up."},{"h":"Related","l":53,"t":"- [[Running a Team Server]] - [[Lifecycle Hooks]] - [[Blocks]] - [[Access Control]] - [[Time Travel]]"}],"tf":{"30":1,"5":1,"a":30,"about":1,"above":1,"access":1,"account":1,"actively":1,"addresses":1,"affordance":1,"after":1,"ahead":1,"alice":2,"already":1,"an":6,"and":11,"another":2,"anyone":1,"anything":1,"apply":1,"are":2,"around":1,"as":4,"at":2,"authentic":1,"author":2,"authorship":1,"auto":4,"away":1,"backstop":1,"between":2,"block":1,"blocks":2,"bob":1,"both":3,"browser":1,"build":1,"but":3,"button":1,"by":3,"can":5,"caret":1,"carries":1,"causal":1,"cdn":1,"change":1,"changes":2,"channel":1,"client":1,"clients":1,"co":2,"collab":1,"coloured":1,"come":1,"comments":2,"commit":5,"commits":3,"committed":2,"committing":1,"conflicts":1,"container":2,"containers":1,"content":1,"continuously":1,"contributed":1,"contributors":1,"control":1,"converges":1,"correctly":1,"crash":2,"crdt":4,"cursor":1,"cut":1,"defeasible":1,"deploy":1,"dies":1,"directory":1,"disconnects":1,"disk":3,"display":2,"do":1,"docs":1,"document":1,"doesn":1,"edit":3,"editing":8,"editor":3,"editors":1,"edits":8,"else":2,"email":1,"engine":2,"ephemeral":1,"events":1,"ever":1,"every":5,"everything":1,"external":3,"extra":1,"few":3,"find":1,"fire":1,"first":1,"flight":2,"floating":1,"for":6,"forgejo":1,"frequency":1,"from":1,"full":1,"git":8,"github":1,"gitlab":1,"gone":1,"google":1,"hard":1,"has":1,"hash":1,"history":1,"hood":1,"hook":2,"hooks":4,"hosts":1,"how":3,"idle":1,"if":9,"in":12,"including":1,"inline":1,"interleave":1,"is":15,"it":7,"its":1,"json":1,"just":1,"keystroke":1,"killed":1,"knows":1,"landed":1,"lands":1,"last":2,"later":1,"layer":2,"let":1,"lifecycle":3,"like":1,"limits":1,"line":1,"list":1,"live":2,"lived":2,"ll":1,"local":3,"log":2,"long":1,"loop":1,"lost":1,"machine":1,"made":1,"markdown":1,"matrix":1,"matter":1,"may":1,"means":1,"media":1,"merge":3,"merges":1,"message":1,"mid":1,"most":1,"moved":1,"multiple":1,"name":2,"navigating":1,"need":1,"new":1,"next":1,"no":2,"notes":1,"nothing":1,"nudge":1,"of":3,"offline":2,"old":1,"older":1,"on":12,"op":1,"open":2,"or":2,"order":1,"out":1,"outside":1,"over":1,"page":8,"paragraphs":1,"party":1,"path":1,"pause":1,"payload":1,"people":1,"per":1,"period":1,"peritext":1,"persist":1,"person":1,"pick":1,"ping":1,"plain":2,"plan":1,"poll":1,"polls":1,"power":1,"process":1,"project":1,"push":1,"quiet":1,"re":2,"read":1,"reads":1,"real":1,"reasoning":1,"receives":1,"reconnect":1,"recovery":1,"redeploy":1,"refreshing":1,"regenerate":1,"registered":1,"related":1,"replayed":1,"repo":1,"restarted":1,"revert":1,"rewrote":1,"rich":1,"run":2,"running":1,"runs":1,"s":6,"same":3,"save":2,"schema":1,"second":2,"seconds":3,"see":5,"selective":1,"send":1,"server":6,"session":1,"set":2,"shas":1,"short":1,"shouldn":1,"show":1,"shows":1,"site":1,"slug":1,"small":2,"so":2,"someone":2,"source":1,"stack":1,"startup":1,"state":1,"static":1,"stdin":1,"still":2,"store":1,"streamed":1,"sync":2,"t":3,"take":1,"team":1,"terminal":1,"text":1,"than":1,"that":5,"the":37,"their":4,"theirs":1,"them":1,"there":1,"they":1,"things":1,"think":2,"third":1,"this":5,"those":1,"tied":1,"time":3,"to":10,"tool":1,"tracked":1,"travel":1,"triggers":1,"true":1,"truth":1,"tunable":1,"two":1,"typing":1,"ui":2,"under":1,"undo":3,"up":3,"user":2,"uses":1,"v0":1,"vanishes":1,"vault":3,"via":1,"volume":1,"wal":2,"web":1,"websocket":1,"were":1,"what":1,"when":3,"where":2,"who":2,"whose":1,"why":1,"will":1,"wire":1,"with":5,"without":3,"work":2,"workflows":1,"would":1,"write":1,"writes":1,"wrote":1,"yet":1,"you":11,"your":7,"zetl":4}},{"dl":435,"n":"Running a Team Server","s":"collaboration/running-a-team-server","secs":[{"h":"Running a Team Server","l":6,"t":"zetl's `--collab` flag turns `zetl serve` into a multi-user editing server: passkey login, real-time CRDT co-editing, per-user git commits, and scoped invitations. Everything is in-tree — there's no feature flag to enable at install time. If you can run `zetl serve`, you can run a team server."},{"h":"The shape of a collab vault","l":10,"t":"A collab vault is a normal Markdown vault plus a `.zetl/collab/` directory: - `.zetl/collab/server.key` — the server's Ed25519 signing key (for session cookies and invitations). - `.zetl/collab/users.db` — SQLite store for passkeys, sessions, and invitation nonces. - `.zetl/collab/wal/` — write-ahead log for the CRDT engine. Survives crashes. These files are created on first run. You never edit them by hand."},{"h":"First run: bootstrap the owner","l":20,"t":"The very first time you start a collab server, you need to bootstrap an owner account. `--init-owner` creates one, prints a 12-word recovery phrase, and exits into the normal serve loop: The terminal will print something like: **Save that phrase.** It's your only way back into the account if you lose your passkey, and zetl will not show it to you a second time. Paper, password manager, encrypted notes file — pick one and commit. Open `http://localhost:3000` in your browser, log in as Alice, and register a passkey (Touch ID, YubiKey, or any WebAuthn authenticator). That's the end of setup. See [[Passkeys and Accounts]] for the full dance."},{"h":"Subsequent starts","l":44,"t":"After the owner exists, you start the server with just `--collab`: The default port is 3000 and the default bind is localhost. Most of the plain-`serve` flags still work — `--port`, `--theme`, `--public`. Two extras are worth knowing: | Flag | When you want it | |------|------------------| | `--hostname mysite.fly.dev` | Public hostname for WebAuthn. Passkeys are bound to a hostname — if you intend to serve over a real domain, set it. Also env: `ZETL_HOSTNAME`. | | `--git-poll-interval 30s` | How often zetl checks `git` for external commits (so an edit you push from another machine shows up in the UI). Set `0` to disable. |"},{"h":"Containerised or ephemeral deployments","l":59,"t":"If your filesystem is ephemeral (Docker, Fly.io, Nomad, a VM you redeploy), the server key in `.zetl/collab/server.key` gets wiped on every redeploy — which invalidates every live session. Not ideal. The fix is a deterministic server key, derived from a BIP39 mnemonic you control: The same mnemonic also derives the git SSH key (via `zetl derive-ssh-key`) and headless agent tokens (via `zetl agent-token`). One seed, three uses. See [[Passkeys and Accounts]] for the derivation paths and [[Co-editing]] for how git push fits in."},{"h":"What's next","l":77,"t":"- **Making accounts:** [[Passkeys and Accounts]]. - **Letting people in:** [[Invitations]]. - **Editing simultaneously:** [[Co-editing]]. - **Scoping who can see what:** [[Access Control]]. - **Publishing read-only without a server at all:** [[Capability URLs]]."},{"h":"Related","l":85,"t":"- [[Web Server]] - [[Installation]] - [[Passkeys and Accounts]] - [[Invitations]] - [[Capability URLs]]"}],"tf":{"12":1,"3000":1,"a":17,"access":1,"account":2,"accounts":5,"after":1,"agent":1,"ahead":1,"alice":1,"all":1,"also":2,"an":2,"and":14,"another":1,"any":1,"are":3,"as":1,"at":2,"authenticator":1,"back":1,"bind":1,"bip39":1,"bootstrap":2,"bound":1,"browser":1,"by":1,"can":3,"capability":2,"checks":1,"co":3,"collab":3,"commit":1,"commits":2,"containerised":1,"control":2,"cookies":1,"crashes":1,"crdt":2,"created":1,"creates":1,"dance":1,"default":2,"deployments":1,"derivation":1,"derived":1,"derives":1,"deterministic":1,"directory":1,"disable":1,"docker":1,"domain":1,"ed25519":1,"edit":2,"editing":5,"enable":1,"encrypted":1,"end":1,"engine":1,"env":1,"ephemeral":2,"every":2,"everything":1,"exists":1,"exits":1,"external":1,"extras":1,"feature":1,"file":1,"files":1,"filesystem":1,"first":3,"fits":1,"fix":1,"flag":3,"flags":1,"fly":1,"for":8,"from":2,"full":1,"gets":1,"git":3,"hand":1,"headless":1,"hostname":2,"how":2,"id":1,"ideal":1,"if":4,"in":7,"install":1,"installation":1,"intend":1,"into":3,"invalidates":1,"invitation":1,"invitations":4,"io":1,"is":6,"it":4,"just":1,"key":4,"knowing":1,"letting":1,"like":1,"live":1,"localhost":1,"log":2,"login":1,"loop":1,"lose":1,"machine":1,"making":1,"manager":1,"markdown":1,"mnemonic":2,"most":1,"multi":1,"need":1,"never":1,"next":1,"no":1,"nomad":1,"nonces":1,"normal":2,"not":2,"notes":1,"of":3,"often":1,"on":2,"one":3,"only":2,"open":1,"or":2,"over":1,"owner":3,"paper":1,"passkey":3,"passkeys":6,"password":1,"paths":1,"people":1,"per":1,"phrase":2,"pick":1,"plain":1,"plus":1,"port":1,"print":1,"prints":1,"public":1,"publishing":1,"push":2,"read":1,"real":2,"recovery":1,"redeploy":2,"register":1,"related":1,"run":4,"running":1,"s":6,"same":1,"save":1,"scoped":1,"scoping":1,"second":1,"see":3,"seed":1,"serve":2,"server":10,"session":2,"sessions":1,"set":2,"setup":1,"shape":1,"show":1,"shows":1,"signing":1,"simultaneously":1,"so":1,"something":1,"sqlite":1,"ssh":1,"start":2,"starts":1,"still":1,"store":1,"subsequent":1,"survives":1,"team":2,"terminal":1,"that":2,"the":21,"them":1,"there":1,"these":1,"three":1,"time":4,"to":6,"tokens":1,"touch":1,"tree":1,"turns":1,"two":1,"ui":1,"up":1,"urls":2,"user":2,"uses":1,"vault":3,"very":1,"via":2,"vm":1,"want":1,"way":1,"web":1,"webauthn":2,"what":2,"when":1,"which":1,"who":1,"will":2,"wiped":1,"with":1,"without":1,"word":1,"work":1,"worth":1,"write":1,"you":13,"your":4,"yubikey":1,"zetl":3}},{"dl":585,"n":"Passkeys and Accounts","s":"collaboration/passkeys-and-accounts","secs":[{"h":"Passkeys and Accounts","l":6,"t":"zetl doesn't do passwords. Instead, every collab account is backed by one or more **WebAuthn passkeys** — Touch ID, Face ID, Windows Hello, a YubiKey, or any FIDO2 authenticator. A 12-word BIP39 **recovery phrase** is the break-glass fallback, and the same phrase deterministically derives the collab server's signing key and a git SSH key."},{"h":"Why passkeys","l":10,"t":"Passwords get phished, reused, and leaked. Passkeys are bound to the origin (so a phishing site can't steal them), require a local user gesture (touch, face, PIN), and never leave your device in usable form. For a small team vault, this is the right default: lower operational burden than password-plus-2FA, and nothing to leak in a database breach."},{"h":"Registering on first login","l":14,"t":"The first time you open a collab server, you log in by **display name** (e.g. `Alice`) and then register a passkey. The browser prompts whatever authenticator is available — Touch ID on a Mac, Windows Hello on a PC, a hardware key, or a phone acting as a security key. Subsequent logins are one tap. No username/password fields. You can register **multiple passkeys** on one account — laptop Touch ID, phone Face ID, a YubiKey in a drawer. If one device dies, the others still work. Manage them from `/_admin/passkeys` in the web UI."},{"h":"Recovery","l":22,"t":"If you lose access to every passkey — laptop stolen, phone dead, YubiKey at the bottom of a river — you recover using your 12-word phrase: 1. Open `/auth/recovery` on the server. 2. Enter your display name and the 12 words. 3. zetl issues a fresh session and deletes all your existing passkeys. 4. Register a new passkey on whatever device you're on now. Write the phrase down when you first see it. Don't type it into a browser extension. Don't email it to yourself. Paper in a drawer, or a line in your password manager, is the sweet spot for a small team."},{"h":"BIP39 and SLIP-0010: one seed, three keys","l":33,"t":"The same 12 words derive three separate keys, at distinct SLIP-0010 paths: | Path | Purpose | How it's used | |------|---------|---------------| | `m/44'/0'/0'` | User account | Generated at `--init-owner`; recovers the passkey-holding account. | | `m/44'/1'/0'` | Server signing key | Passed to `serve --collab --server-key-seed`. See [[Running a Team Server]]. | | `m/44'/2'/0'` | Git SSH key | Derived on demand with `zetl derive-ssh-key`. | The paths are deterministic: given the phrase, the key is always the same. This is why a single mnemonic safely covers an ephemeral redeploy — the new container derives the identical server key, so existing sessions keep working."},{"h":"Reproducing the SSH key anywhere","l":45,"t":"If you push the vault's git repo to a remote (GitLab, GitHub, Forgejo, codeberg), you want a stable SSH key for the remote. `zetl derive-ssh-key` materialises the ed25519 key from the mnemonic: It writes the private key to `--out` (or stdout if omitted) and prints the public key so you can paste it into your git host's SSH settings. Redeploy the server a year later, derive again, same key — no need to rotate the git remote. You can also derive headless **agent tokens** from the same seed for CI and bots: Use the printed token as `Authorization: Bearer <token>` against the zetl API."},{"h":"A note on threat models","l":66,"t":"Passkeys protect you from remote phishing. They don't protect you from a coworker who sits at your unlocked laptop. The recovery phrase is the single highest-value secret in the system — treat it as such. See [[Access Control]] for server-side scoping of what each account can actually see."},{"h":"Related","l":70,"t":"- [[Running a Team Server]] - [[Invitations]] - [[Access Control]] - [[Co-editing]] - [[Installation]]"}],"tf":{"0010":2,"1":1,"12":4,"2":1,"2fa":1,"3":1,"4":1,"a":31,"access":3,"account":5,"accounts":1,"acting":1,"actually":1,"again":1,"against":1,"agent":1,"all":1,"also":1,"always":1,"an":1,"and":12,"any":1,"anywhere":1,"api":1,"are":3,"as":3,"at":4,"authenticator":2,"available":1,"backed":1,"bip39":2,"bots":1,"bottom":1,"bound":1,"breach":1,"break":1,"browser":2,"burden":1,"by":2,"can":5,"ci":1,"co":1,"codeberg":1,"collab":3,"container":1,"control":2,"covers":1,"coworker":1,"database":1,"dead":1,"default":1,"deletes":1,"demand":1,"derive":3,"derived":1,"derives":2,"deterministic":1,"deterministically":1,"device":3,"dies":1,"display":2,"distinct":1,"do":1,"doesn":1,"don":3,"down":1,"drawer":2,"e":1,"each":1,"ed25519":1,"editing":1,"email":1,"enter":1,"ephemeral":1,"every":2,"existing":2,"extension":1,"face":3,"fallback":1,"fido2":1,"fields":1,"first":3,"for":5,"forgejo":1,"form":1,"fresh":1,"from":5,"g":1,"generated":1,"gesture":1,"get":1,"git":5,"github":1,"gitlab":1,"given":1,"glass":1,"hardware":1,"headless":1,"hello":2,"highest":1,"holding":1,"host":1,"how":1,"id":5,"identical":1,"if":4,"in":8,"installation":1,"instead":1,"into":2,"invitations":1,"is":8,"issues":1,"it":7,"keep":1,"key":14,"keys":2,"laptop":3,"later":1,"leak":1,"leaked":1,"leave":1,"line":1,"local":1,"log":1,"login":1,"logins":1,"lose":1,"lower":1,"mac":1,"manage":1,"manager":1,"materialises":1,"mnemonic":2,"models":1,"more":1,"multiple":1,"name":2,"need":1,"never":1,"new":2,"no":2,"note":1,"nothing":1,"now":1,"of":2,"omitted":1,"on":9,"one":5,"open":2,"operational":1,"or":5,"origin":1,"others":1,"paper":1,"passed":1,"passkey":4,"passkeys":7,"password":3,"passwords":2,"paste":1,"path":1,"paths":2,"pc":1,"phished":1,"phishing":2,"phone":3,"phrase":6,"pin":1,"plus":1,"printed":1,"prints":1,"private":1,"prompts":1,"protect":2,"public":1,"purpose":1,"push":1,"re":1,"recover":1,"recovers":1,"recovery":3,"redeploy":2,"register":3,"registering":1,"related":1,"remote":4,"repo":1,"reproducing":1,"require":1,"reused":1,"right":1,"river":1,"rotate":1,"running":2,"s":4,"safely":1,"same":5,"scoping":1,"secret":1,"security":1,"see":4,"seed":2,"separate":1,"server":9,"session":1,"sessions":1,"settings":1,"side":1,"signing":2,"single":2,"site":1,"sits":1,"slip":2,"small":2,"so":3,"spot":1,"ssh":5,"stable":1,"stdout":1,"steal":1,"still":1,"stolen":1,"subsequent":1,"such":1,"sweet":1,"system":1,"t":5,"tap":1,"team":4,"than":1,"the":37,"them":2,"then":1,"they":1,"this":2,"threat":1,"three":2,"time":1,"to":8,"token":1,"tokens":1,"touch":4,"treat":1,"type":1,"ui":1,"unlocked":1,"usable":1,"use":1,"used":1,"user":2,"username":1,"using":1,"value":1,"vault":2,"want":1,"web":1,"webauthn":1,"what":1,"whatever":2,"when":1,"who":1,"why":2,"windows":2,"with":1,"word":2,"words":2,"work":1,"working":1,"write":1,"writes":1,"year":1,"you":13,"your":7,"yourself":1,"yubikey":3,"zetl":3}},{"dl":801,"n":"Capability URLs","s":"collaboration/capability-urls","secs":[{"h":"Capability URLs","l":6,"t":"**Capability mode** is zetl's answer to: \"I want to share my wiki with a dozen friends, read-only, without running a server, but I don't want the whole internet reading it.\" The output is a plain static site — hostable on any CDN, any S3 bucket, any sftp'd directory — where access is gated by a **secret in the URL fragment**. No accounts, no server-side auth, nothing to rotate but the URLs themselves. If you want multi-user editing, you want [[Running a Team Server]]. Capability mode is for publishing."},{"h":"The idea in one paragraph","l":12,"t":"Each page is encrypted at build time with a cohort-specific key. Invite URLs look like `https://wiki.example.com/welcome.html#k=<base64-secret>`. The `#fragment` — by design of the HTTP spec — is never sent to the server. The reader's browser, executing a small JavaScript shim, pulls the key out of the fragment, decrypts the page locally, and verifies a vault-level Ed25519 signature so nobody can serve them a forged page. Revocation is per-cohort: you rotate the cohort's salt and re-deploy, old URLs stop decrypting. Formal spec: see `docs/capability-mode.md` in the zetl repo."},{"h":"Setting up a capability site","l":18,"t":""},{"h":"1. Generate the keys","l":20,"t":"This prints two secrets to stdout, exactly once: - `ZETL_CAP_SECRET` — the 48-byte content-encryption secret. - `ZETL_CAP_SIGNING_KEY` — the Ed25519 vault-signing private key. Capture them somewhere safe (a secret store, a password manager, a sealed envelope). You'll need `ZETL_CAP_SECRET` on every build and `ZETL_CAP_SIGNING_KEY` whenever you rotate the signing key."},{"h":"2. Issue an invite","l":33,"t":"`--cohort` groups grants that share a content key. Rotate a cohort without touching the others. `--site-url` is the canonical hostname the invite URL points at (also settable via `ZETL_CAP_SITE_URL`). The command prints an invite URL like: Send it by whatever channel works — Signal, in-person, a QR code. The part after `#` is the secret; if you leak the URL, you leak read access. Narrow an invite to a subset of pages with `--pages`, and set a TTL with `--expires`:"},{"h":"3. List, revoke, rotate","l":61,"t":"After revoke or rotate, **rebuild and redeploy**. The old ciphertext is still out there on anyone's browser cache, but new ciphertext won't decrypt under an old key, and a well-configured CDN will have dropped the stale response by the time a reader returns."},{"h":"Two-operator handoff: `zetl cap pair`","l":78,"t":"For higher-stakes vaults, you don't want the inviter to see the reader's private key even briefly. `zetl cap pair` runs a **SPAKE2 pubkey handoff** authenticated by a short spoken phrase: - **Grantor** (you) runs `zetl cap pair --grantor`. It generates a fresh 4-word BIP39 phrase, prints it, and starts a SPAKE2 session. You read the phrase aloud to the other operator. - **Grantee** (the other operator) runs `zetl cap pair --grantee --peer <handshake> --phrase \"<4 words>\" --pubkey <their-pubkey>`. They send their outbound handshake and HMAC tag back to you. - You paste those into the blocked grantor session. On a matching phrase, SPAKE2 derives the same shared key on both sides and the HMAC verifies; the authenticated pubkey is now yours to use with `zetl cap invite --recipient`. If the phrase differs — because someone in the middle tried to substitute one — the HMAC fails and nothing goes through. This is the \"high-value vault\" workflow; for friends-and-family scale, the default delegated URL mode is fine."},{"h":"Split-key mode for high-value vaults","l":88,"t":"A capability URL is bearer-auth: anyone with the URL reads. For truly sensitive cohorts, `--split-key` divides the private key between the URL fragment and a **second factor** — either a spoken phrase the reader types in, or a QR code from a separate device — so neither alone unlocks the vault: Split-key mode must be enabled in the vault config first (`[access.split_key] enabled = true`). Without that, `--split-key` is rejected. The second factor is a build-time configuration; default is `spoken-phrase` (the reader types it in), and `qr` is a planned alternative."},{"h":"Other useful subcommands","l":101,"t":"| Command | What it does | |---------|--------------| | `zetl cap finalise <grant>` | Mark a grant as operator-confirmed after first use, so the capability binds to a known device (TOFU). | | `zetl cap check` | Stale-grant and public-safety audit. Catches capabilities drifting past their useful life. | | `zetl cap sweep` | Bulk-revoke past-expiry grants. | | `zetl cap audit-diff` | Scan a vault diff for malicious-content patterns (e.g. a hostile theme attempting exfiltration). | | `zetl cap rotate-signing-key` | Rotate the vault signing key. Rebuild required — every page is re-signed. | | `zetl cap emergency-shutdown` | Print the operator checklist for pulling a capability site offline in a hurry. Doesn't modify files. |"},{"h":"When to use capability URLs vs a collab server","l":112,"t":"- **Capability URLs**: publishing read-only to a known group. No server to run. No accounts to manage. Hostable anywhere static. Revocation is per-cohort, not per-user-finely — if you need to yank one reader mid-cohort, that's a rotate. - **Collab server**: two or more people actively editing. Real-time merge. Per-user commits. Needs a box to run `zetl serve --collab` on. For the full operator spec — threat model, deploy recipes for Nginx/CloudFront, rotation ordering, CSP headers — see `docs/capability-mode.md` in the zetl repo."},{"h":"Related","l":119,"t":"- [[Running a Team Server]] - [[Static Site Export]] - [[Invitations]] - [[Access Control]] - [[Web Server]]"}],"tf":{"1":1,"2":1,"3":1,"4":1,"48":1,"a":43,"access":3,"accounts":2,"actively":1,"after":3,"alone":1,"aloud":1,"also":1,"alternative":1,"an":4,"and":14,"answer":1,"any":3,"anyone":2,"anywhere":1,"as":1,"at":2,"attempting":1,"audit":1,"auth":2,"authenticated":2,"back":1,"be":1,"bearer":1,"because":1,"between":1,"binds":1,"bip39":1,"blocked":1,"both":1,"box":1,"briefly":1,"browser":2,"bucket":1,"build":3,"bulk":1,"but":3,"by":5,"byte":1,"cache":1,"can":1,"canonical":1,"capabilities":1,"capability":9,"capture":1,"catches":1,"cdn":2,"channel":1,"checklist":1,"ciphertext":2,"cloudfront":1,"code":2,"cohort":6,"cohorts":1,"collab":2,"command":2,"commits":1,"config":1,"configuration":1,"configured":1,"confirmed":1,"content":3,"control":1,"csp":1,"d":1,"decrypt":1,"decrypting":1,"decrypts":1,"default":2,"delegated":1,"deploy":2,"derives":1,"design":1,"device":2,"diff":1,"differs":1,"directory":1,"divides":1,"does":1,"doesn":1,"don":2,"dozen":1,"drifting":1,"dropped":1,"e":1,"each":1,"ed25519":2,"editing":2,"either":1,"enabled":1,"encrypted":1,"encryption":1,"envelope":1,"even":1,"every":2,"exactly":1,"executing":1,"exfiltration":1,"expiry":1,"export":1,"factor":2,"fails":1,"family":1,"files":1,"fine":1,"finely":1,"first":2,"for":9,"forged":1,"formal":1,"fragment":3,"fresh":1,"friends":2,"from":1,"full":1,"g":1,"gated":1,"generate":1,"generates":1,"goes":1,"grant":2,"grantee":1,"grantor":2,"grants":2,"group":1,"groups":1,"handoff":2,"handshake":1,"have":1,"headers":1,"high":2,"higher":1,"hmac":3,"hostable":2,"hostile":1,"hostname":1,"http":1,"hurry":1,"i":2,"idea":1,"if":4,"in":10,"internet":1,"into":1,"invitations":1,"invite":5,"inviter":1,"is":20,"issue":1,"it":6,"javascript":1,"key":12,"keys":1,"known":2,"leak":2,"level":1,"life":1,"like":2,"list":1,"ll":1,"locally":1,"look":1,"malicious":1,"manage":1,"manager":1,"mark":1,"matching":1,"merge":1,"mid":1,"middle":1,"mode":5,"model":1,"modify":1,"more":1,"multi":1,"must":1,"my":1,"narrow":1,"need":2,"needs":1,"neither":1,"never":1,"new":1,"nginx":1,"no":4,"nobody":1,"not":1,"nothing":2,"now":1,"of":3,"offline":1,"old":3,"on":6,"once":1,"one":3,"only":2,"operator":6,"or":3,"ordering":1,"other":3,"others":1,"out":2,"outbound":1,"output":1,"page":4,"pages":1,"paragraph":1,"part":1,"password":1,"past":2,"paste":1,"patterns":1,"people":1,"per":4,"person":1,"phrase":6,"plain":1,"planned":1,"points":1,"print":1,"prints":3,"private":3,"pubkey":2,"public":1,"publishing":2,"pulling":1,"pulls":1,"qr":2,"re":2,"read":4,"reader":6,"reading":1,"reads":1,"real":1,"rebuild":2,"recipes":1,"redeploy":1,"rejected":1,"related":1,"repo":2,"required":1,"response":1,"returns":1,"revocation":2,"revoke":3,"rotate":8,"rotation":1,"run":2,"running":3,"runs":3,"s":6,"s3":1,"safe":1,"safety":1,"salt":1,"same":1,"scale":1,"scan":1,"sealed":1,"second":2,"secret":4,"secrets":1,"see":3,"send":2,"sensitive":1,"sent":1,"separate":1,"serve":1,"server":9,"session":2,"set":1,"settable":1,"setting":1,"sftp":1,"share":2,"shared":1,"shim":1,"short":1,"side":1,"sides":1,"signal":1,"signature":1,"signed":1,"signing":3,"site":4,"small":1,"so":3,"someone":1,"somewhere":1,"spake2":3,"spec":3,"specific":1,"split":2,"spoken":2,"stakes":1,"stale":2,"starts":1,"static":3,"stdout":1,"still":1,"stop":1,"store":1,"subcommands":1,"subset":1,"substitute":1,"t":4,"tag":1,"team":2,"that":3,"the":55,"their":2,"them":2,"theme":1,"themselves":1,"there":1,"they":1,"this":2,"those":1,"threat":1,"through":1,"time":4,"to":18,"tofu":1,"touching":1,"tried":1,"truly":1,"ttl":1,"two":3,"types":2,"under":1,"unlocks":1,"up":1,"url":8,"urls":6,"use":3,"useful":2,"user":3,"value":2,"vault":7,"vaults":2,"verifies":2,"via":1,"vs":1,"want":5,"web":1,"well":1,"what":1,"whatever":1,"when":1,"whenever":1,"where":1,"whole":1,"wiki":1,"will":1,"with":6,"without":3,"won":1,"word":1,"workflow":1,"works":1,"yank":1,"you":13,"yours":1,"zetl":3}},{"dl":437,"n":"Backlinks","s":"finding/backlinks","secs":[{"h":"Backlinks","l":6,"t":"`zetl backlinks \"Page\"` lists every page that links **to** `Page`. It is the quiet workhorse of a wikilink system — more useful, over time, than the links you write yourself. Output: the file path, the line of the match, and (with `--context N`) a snippet of the surrounding prose."},{"h":"Why backlinks matter","l":16,"t":"Forward links are a claim you made at the time of writing. A backlink is every claim anyone ever made that **referred back to** this idea — including you, six months later, with more context. That asymmetry is the whole research payoff: - You read a paper and start a note on it. A year later the note has eight backlinks: three from project docs, two from meeting minutes, one from a book review. You did not plan any of that. The graph assembled it. - You are revisiting an old concept page. Instead of re-reading it in isolation, you walk its backlinks — every context in which you once found it relevant. That is a research tool disguised as a list. Forward links (see [[Following Links]]) answer *what does this page talk about?* Backlinks answer *what talks about this page?* The second question is almost always more interesting for notes older than a few weeks."},{"h":"Multi-hop with `--depth`","l":25,"t":"By default `zetl backlinks` returns direct, one-hop references. `--depth N` walks the reverse graph: Depth 2–3 is where the transitive structure gets interesting: you find the notes that cite notes that cite this concept. Depth 5+ tends to return most of your vault — the graph is small-world."},{"h":"Example: walking backwards from a concept","l":39,"t":"You have a page called `CRDT`. You want to see every context in which you once thought CRDTs were relevant, so you can decide whether your 2026 research plan should build on them. Read the backlinks in the order printed (depth-first, then alphabetical). For each one, ask: *did I mean the same thing by CRDT here as I do now?* You will almost always find one or two notes where you used the term loosely, and one or two where you made a stronger claim than you remembered."},{"h":"Useful flags","l":53,"t":"| Flag | Use | |------|-----| | `--context 80` | Show 80 characters of snippet around each link | | `--depth 2` | Multi-hop reverse traversal | | `--fuzzy` | Tolerate typos in the page name | | `--with-conclusions` | Show SPL conclusions each linker contributes (requires `--features reason`) | See [[CLI Overview]] for the rest."},{"h":"JSON output","l":64,"t":"Piped output auto-switches to JSON. Useful for feeding backlink lists to an AI agent, or building \"related reading\" sections programmatically:"},{"h":"Time travel","l":73,"t":"`--at \"last monday\"` runs the query against a past snapshot. Requires `--features history`. Answer: *whose backlinks did this page have before I started the refactor?* See [[Time Travel]]."},{"h":"Related","l":77,"t":"- [[Following Links]] - [[The Link Graph]] - [[Searching]] - [[Finding Orphans and Dead Links]]"}],"tf":{"2":1,"2026":1,"3":1,"5":1,"80":1,"a":15,"about":2,"against":1,"agent":1,"ai":1,"almost":2,"alphabetical":1,"always":2,"an":2,"and":4,"answer":3,"any":1,"anyone":1,"are":2,"around":1,"as":2,"ask":1,"assembled":1,"asymmetry":1,"at":1,"auto":1,"back":1,"backlink":2,"backlinks":7,"backwards":1,"before":1,"book":1,"build":1,"building":1,"by":2,"called":1,"can":1,"characters":1,"cite":2,"claim":3,"cli":1,"concept":3,"conclusions":1,"context":3,"contributes":1,"crdt":1,"crdts":1,"dead":1,"decide":1,"default":1,"depth":3,"did":3,"direct":1,"disguised":1,"do":1,"docs":1,"does":1,"each":3,"eight":1,"ever":1,"every":4,"example":1,"feeding":1,"few":1,"file":1,"find":2,"finding":1,"first":1,"flag":1,"flags":1,"following":2,"for":4,"forward":2,"found":1,"from":4,"gets":1,"graph":4,"has":1,"have":2,"here":1,"hop":3,"i":3,"idea":1,"in":5,"including":1,"instead":1,"interesting":2,"is":7,"isolation":1,"it":5,"its":1,"json":2,"later":2,"line":1,"link":2,"linker":1,"links":7,"list":1,"lists":2,"loosely":1,"made":3,"match":1,"matter":1,"mean":1,"meeting":1,"minutes":1,"months":1,"more":3,"most":1,"multi":2,"name":1,"not":1,"note":2,"notes":4,"now":1,"of":8,"old":1,"older":1,"on":2,"once":2,"one":5,"or":3,"order":1,"orphans":1,"output":3,"over":1,"overview":1,"page":7,"paper":1,"past":1,"path":1,"payoff":1,"piped":1,"plan":2,"printed":1,"programmatically":1,"project":1,"prose":1,"query":1,"question":1,"quiet":1,"re":1,"read":2,"reading":2,"refactor":1,"references":1,"referred":1,"related":2,"relevant":2,"remembered":1,"requires":2,"research":3,"rest":1,"return":1,"returns":1,"reverse":2,"review":1,"revisiting":1,"runs":1,"same":1,"searching":1,"second":1,"sections":1,"see":4,"should":1,"show":2,"six":1,"small":1,"snapshot":1,"snippet":2,"so":1,"spl":1,"start":1,"started":1,"stronger":1,"structure":1,"surrounding":1,"switches":1,"system":1,"talk":1,"talks":1,"tends":1,"term":1,"than":3,"that":7,"the":24,"them":1,"then":1,"thing":1,"this":5,"thought":1,"three":1,"time":4,"to":6,"tolerate":1,"tool":1,"transitive":1,"travel":2,"traversal":1,"two":3,"typos":1,"use":1,"used":1,"useful":3,"vault":1,"walk":1,"walking":1,"walks":1,"want":1,"weeks":1,"were":1,"what":2,"where":3,"whether":1,"which":2,"whole":1,"whose":1,"why":1,"wikilink":1,"will":1,"with":3,"workhorse":1,"world":1,"write":1,"writing":1,"year":1,"you":17,"your":2,"yourself":1}},{"dl":384,"n":"Following Links","s":"finding/following-links","secs":[{"h":"Following Links","l":6,"t":"Three commands walk the forward-link graph: `zetl links` for a page's neighbours, `zetl path` for the shortest route between two pages, and `zetl export` for dumping the whole graph as JSON. Together they turn your vault into a navigable graph you can query from the shell."},{"h":"`zetl links` — forward neighbours","l":10,"t":"Prints every page that `Zettelkasten Method` links **to**. This is the complement of [[Backlinks]]: forward links are what this page cites; backlinks are what cites it. Walk further with `--depth`: Useful flags: | Flag | Use | |------|-----| | `--depth N` | Multi-hop forward traversal | | `--context 80` | Show surrounding prose of each link | | `--fuzzy` | Tolerate typos in the source page name | See [[CLI Overview]] for the rest."},{"h":"`zetl path` — shortest route between two pages","l":38,"t":"This answers *how is X connected to Y?* — a question that comes up constantly when you are trying to explain a decision (\"why did I end up thinking about Luhmann while researching CRDTs?\"). zetl performs an undirected BFS over wikilinks and returns the first shortest path. Tune the search with `--max-depth N` (default 10). Raise it for large vaults where the connection is genuinely distant; lower it to fail fast when you suspect there is no path at all. No path found is a meaningful answer: these two notes live in disconnected components of your graph, and that is a real fact about your thinking."},{"h":"`zetl export` — the full graph as JSON","l":62,"t":"Writes the entire vault graph: every page, every edge, every frontmatter block. This is the integration point for everything outside zetl — graph-viz tools, AI agents, custom dashboards, academic network analysis scripts. The schema is stable and documented at [[CLI Overview]]. A trimmed view:"},{"h":"Practical uses","l":86,"t":"**Graph visualisation.** Pipe the export into Gephi, Cytoscape, or any force-directed tool: **AI agents.** Feed the full graph to an agent so it can ground answers in your own note topology. The MCP server ([[MCP Server]]) exposes a thinner version of the same data. **Diffing graphs over time.** Combine with `--at` (requires `--features history`) to compare your graph now vs. a month ago:"},{"h":"Example: explain a decision","l":107,"t":"You made a call in `2026 Research Plan` to build on CRDTs. A colleague asks why. You retrace the chain: Each hop is a note you wrote with its own reasoning. Open them in order with `zetl view` and you have the audit trail — not reconstructed from memory, but from the links you dropped as you thought."},{"h":"Related","l":121,"t":"- [[Backlinks]] - [[The Link Graph]] - [[Searching]] - [[MCP Server]]"}],"tf":{"10":1,"a":13,"about":2,"academic":1,"agent":1,"agents":2,"ago":1,"ai":2,"all":1,"an":2,"analysis":1,"and":5,"answer":1,"answers":2,"any":1,"are":3,"as":3,"asks":1,"at":2,"audit":1,"backlinks":3,"between":2,"bfs":1,"block":1,"build":1,"but":1,"call":1,"can":2,"chain":1,"cites":2,"cli":2,"colleague":1,"combine":1,"comes":1,"commands":1,"compare":1,"complement":1,"components":1,"connected":1,"connection":1,"constantly":1,"crdts":2,"custom":1,"cytoscape":1,"dashboards":1,"data":1,"decision":2,"default":1,"did":1,"diffing":1,"directed":1,"disconnected":1,"distant":1,"documented":1,"dropped":1,"dumping":1,"each":2,"edge":1,"end":1,"entire":1,"every":4,"everything":1,"example":1,"explain":2,"export":1,"exposes":1,"fact":1,"fail":1,"fast":1,"feed":1,"first":1,"flag":1,"flags":1,"following":1,"for":6,"force":1,"forward":4,"found":1,"from":3,"frontmatter":1,"full":2,"further":1,"genuinely":1,"gephi":1,"graph":11,"graphs":1,"ground":1,"have":1,"hop":2,"how":1,"i":1,"in":5,"integration":1,"into":2,"is":9,"it":4,"its":1,"json":2,"large":1,"link":3,"links":4,"live":1,"lower":1,"luhmann":1,"made":1,"mcp":3,"meaningful":1,"memory":1,"month":1,"multi":1,"name":1,"navigable":1,"neighbours":2,"network":1,"no":2,"not":1,"note":2,"notes":1,"now":1,"of":4,"on":1,"open":1,"or":1,"order":1,"outside":1,"over":2,"overview":2,"own":2,"page":5,"pages":2,"path":3,"performs":1,"pipe":1,"point":1,"practical":1,"prints":1,"prose":1,"query":1,"question":1,"raise":1,"real":1,"reasoning":1,"reconstructed":1,"related":1,"requires":1,"researching":1,"rest":1,"retrace":1,"returns":1,"route":2,"s":1,"same":1,"schema":1,"scripts":1,"search":1,"searching":1,"see":1,"server":3,"shell":1,"shortest":3,"show":1,"so":1,"source":1,"stable":1,"surrounding":1,"suspect":1,"that":3,"the":22,"them":1,"there":1,"these":1,"they":1,"thinking":2,"thinner":1,"this":4,"thought":1,"three":1,"time":1,"to":7,"together":1,"tolerate":1,"tool":1,"tools":1,"topology":1,"trail":1,"traversal":1,"trimmed":1,"trying":1,"tune":1,"turn":1,"two":3,"typos":1,"undirected":1,"up":2,"use":1,"useful":1,"uses":1,"vault":2,"vaults":1,"version":1,"view":1,"visualisation":1,"viz":1,"vs":1,"walk":2,"what":2,"when":2,"where":1,"while":1,"whole":1,"why":2,"wikilinks":1,"with":5,"writes":1,"wrote":1,"x":1,"y":1,"you":9,"your":5,"zetl":2}},{"dl":542,"n":"Finding Orphans and Dead Links","s":"finding/finding-orphans-and-dead-links","secs":[{"h":"Finding Orphans and Dead Links","l":6,"t":"`zetl check` is the vault-health command. It reports three kinds of problem — dead links, orphan pages, and syntax errors — and optionally a fourth, SPL diagnostics, if you use the reasoning layer. Run it before a big refactor, before a publish, and in CI. Exits non-zero if anything at error level is found. That makes it a drop-in CI step."},{"h":"The two main health signals","l":16,"t":""},{"h":"Dead links","l":18,"t":"A **dead link** is a `[[wikilink]]` that points at a page that does not exist. You typed `[[Zettelkasten Methog]]` and meant `[[Zettelkasten Method]]`; or you deleted a page without updating the notes that cited it. Two ways to fix each one: 1. **Create the page.** If the link represents a real idea that deserves a page, the dead link is a prompt: go write the note. This is the positive use of dead links — they are a TODO list generated by your past self. 2. **Rename or delete the link.** If the target was a typo, fix the typo. If the idea no longer deserves a page, remove the link or replace it with plain prose. The `zetl similar \"Zettelkasten Methog\"` command (see [[Similar Pages]]) pairs naturally with dead-link review: for each dead link, check whether you meant an existing page."},{"h":"Orphans","l":33,"t":"An **orphan** is a page with no inbound links **and** no outbound links. Nothing points at it; it points at nothing. It is disconnected from the graph. Two ways to treat each one: - **Link it.** If the page is real content (a book note, a meeting summary), wire it in. Add a `[[link]]` from an index page, a project page, or a daily note. This is usually the right move. - **Delete it.** If the orphan is a stale draft, a duplicate, or a half-thought you abandoned, delete it. Orphans are not neutral; they decay the signal-to-noise ratio of [[Searching]] and of your own browsing. A zero-orphan vault is not a goal. Some orphans are intentional — a daily journal entry, say, that you never expect to link into the graph. Skim the list, act on the fixable ones, leave the rest."},{"h":"Syntax errors","l":48,"t":"`zetl check --syntax` reports malformed wikilinks, malformed frontmatter, and other parse-level problems. These are usually copy-paste accidents. Fix them on sight."},{"h":"`zetl stats` — the summary view","l":52,"t":"A pre-flight glance. Run it weekly; watch the dead-link and orphan counts trend. An unchanged dead-link count for a month means you are not actually reviewing them. `--top N` widens the most-linked section (default 10)."},{"h":"CI integration","l":70,"t":"The `--fail-on` flag controls exit code behaviour. Default is `error`; bump to `warning` if you want a strict CI that fails on anything at all. A typical pipeline step: JSON output is automatic under a pipe, so you can also post results to a dashboard:"},{"h":"SPL diagnostics","l":95,"t":"If you use the reasoning layer ([[What is SPL]]), `--spl` surfaces Spindle-Lisp-specific problems: parse errors, duplicate labels, undefined references, unreachable literals. Requires `--features reason`. A related flag, `--drift`, reports SPL blocks whose grounding facts have changed since the last theory build — the \"your logic block is stale\" warning."},{"h":"Example: pre-publish pass","l":105,"t":"You are about to `zetl build` your public notes vault. A three-minute pass before the build: The exit code of the fourth command tells you whether to proceed."},{"h":"Related","l":120,"t":"- [[Backlinks]] - [[Similar Pages]] - [[Writing Pages]] - [[CLI Overview]]"}],"tf":{"1":1,"10":1,"2":1,"a":34,"abandoned":1,"about":1,"accidents":1,"act":1,"actually":1,"add":1,"all":1,"also":1,"an":4,"and":9,"anything":2,"are":6,"at":5,"automatic":1,"backlinks":1,"before":3,"behaviour":1,"big":1,"block":1,"blocks":1,"book":1,"browsing":1,"build":2,"bump":1,"by":1,"can":1,"changed":1,"check":1,"ci":4,"cited":1,"cli":1,"code":2,"command":3,"content":1,"controls":1,"copy":1,"count":1,"counts":1,"create":1,"daily":2,"dashboard":1,"dead":10,"decay":1,"default":2,"delete":3,"deleted":1,"deserves":2,"diagnostics":2,"disconnected":1,"does":1,"draft":1,"drop":1,"duplicate":2,"each":3,"entry":1,"error":1,"errors":3,"example":1,"exist":1,"existing":1,"exit":2,"exits":1,"expect":1,"facts":1,"fails":1,"finding":1,"fix":3,"fixable":1,"flag":2,"flight":1,"for":2,"found":1,"fourth":2,"from":2,"frontmatter":1,"generated":1,"glance":1,"go":1,"goal":1,"graph":2,"grounding":1,"half":1,"have":1,"health":2,"idea":2,"if":9,"in":3,"inbound":1,"index":1,"integration":1,"intentional":1,"into":1,"is":15,"it":13,"journal":1,"json":1,"kinds":1,"labels":1,"last":1,"layer":2,"leave":1,"level":2,"link":11,"linked":1,"links":6,"lisp":1,"list":2,"literals":1,"logic":1,"longer":1,"main":1,"makes":1,"malformed":2,"means":1,"meant":2,"meeting":1,"minute":1,"month":1,"most":1,"move":1,"naturally":1,"neutral":1,"never":1,"no":3,"noise":1,"non":1,"not":4,"note":3,"notes":2,"nothing":2,"of":5,"on":3,"one":2,"ones":1,"optionally":1,"or":5,"orphan":5,"orphans":4,"other":1,"outbound":1,"output":1,"overview":1,"own":1,"page":10,"pages":4,"pairs":1,"parse":2,"pass":2,"past":1,"paste":1,"pipe":1,"pipeline":1,"plain":1,"points":3,"positive":1,"post":1,"pre":2,"problem":1,"problems":2,"proceed":1,"project":1,"prompt":1,"prose":1,"public":1,"publish":2,"ratio":1,"real":2,"reasoning":2,"refactor":1,"references":1,"related":2,"remove":1,"rename":1,"replace":1,"reports":3,"represents":1,"requires":1,"rest":1,"results":1,"review":1,"reviewing":1,"right":1,"run":2,"say":1,"searching":1,"section":1,"see":1,"self":1,"sight":1,"signal":1,"signals":1,"similar":2,"since":1,"skim":1,"so":1,"some":1,"specific":1,"spindle":1,"spl":4,"stale":2,"step":2,"strict":1,"summary":2,"surfaces":1,"syntax":2,"target":1,"tells":1,"that":7,"the":34,"them":2,"theory":1,"these":1,"they":2,"this":2,"thought":1,"three":2,"to":8,"todo":1,"treat":1,"trend":1,"two":3,"typed":1,"typical":1,"typo":2,"unchanged":1,"undefined":1,"under":1,"unreachable":1,"updating":1,"use":3,"usually":2,"vault":3,"view":1,"want":1,"warning":1,"was":1,"watch":1,"ways":2,"weekly":1,"what":1,"whether":2,"whose":1,"widens":1,"wikilinks":1,"wire":1,"with":3,"without":1,"write":1,"writing":1,"you":12,"your":4,"zero":2}},{"dl":398,"n":"Similar Pages","s":"finding/similar-pages","secs":[{"h":"Similar Pages","l":6,"t":"`zetl similar \"Query\"` finds pages whose **titles** are near-matches for the query string. It uses SimHash over page names, which makes it excellent at three things: recovering from typos, spotting duplicate-ish pages you meant to merge, and suggesting links as you type. The number is **Hamming distance** on the hash — smaller is more similar. Zero would be identical."},{"h":"What SimHash does, briefly","l":22,"t":"SimHash is a locality-sensitive hash: feed it a string, get back a fixed-length binary fingerprint such that similar strings produce similar fingerprints. Unlike a cryptographic hash (where flipping one letter scrambles the whole output), SimHash changes only a few bits when the input changes slightly. Comparing two hashes by Hamming distance — the count of differing bits — gives you a cheap similarity score without storing the originals. zetl computes one hash per page name at index time. `zetl similar` then scans them and returns the closest N. No full-text comparison, no embedding model, no network — just arithmetic over bits. It finishes in under a millisecond for vaults of tens of thousands of pages. The tradeoff: it only sees **names**. A page titled `Dogs` and a page titled `Canine Companions` are not similar to SimHash, even though they are the same topic. For content similarity, use `zetl search --semantic` (see [[Searching]])."},{"h":"The three flags","l":30,"t":"| Flag | Default | Use | |------|---------|-----| | `--threshold N` | `12` | Max Hamming distance to report. Lower = stricter | | `--limit N` | `10` | Max results | | `--json` | off | Force JSON output | The default threshold of 12 is tuned to catch typos and near-duplicates without flooding the output. Lower it to 6 if you only want obvious matches; raise it to 20 to see the long tail."},{"h":"When to use it","l":40,"t":"**Recovering from typos.** You vaguely remember a page name, try a spelling, and zetl says \"no such page.\" **Catching duplicates.** Over time you accumulate `Deep Work`, `Deep Work (book)`, and `Deep Work Notes`. A periodic `zetl similar` sweep on your top-linked pages surfaces these before the graph splits in half. **Suggesting links while writing.** Before creating a new page, run `zetl similar \"Proposed Title\"`. If there is a page at distance 4, you probably meant to link to it, not spawn a sibling."},{"h":"Example: weekly duplicate audit","l":60,"t":"A one-liner to find every near-duplicate pair in the vault, suitable for a Sunday cleanup: Run it once a month; act on the ones you recognise. Most will be false positives (e.g. `2026-04-01` and `2026-04-02` share nearly every bit) — the ones that matter will stand out."},{"h":"Related","l":76,"t":"- [[Searching]] - [[Linking Pages]] - [[Finding Orphans and Dead Links]] - [[CLI Overview]]"}],"tf":{"12":1,"20":1,"4":1,"6":1,"a":18,"accumulate":1,"act":1,"and":8,"are":3,"arithmetic":1,"as":1,"at":3,"audit":1,"back":1,"be":2,"before":2,"binary":1,"bit":1,"bits":3,"briefly":1,"by":1,"catch":1,"catching":1,"changes":2,"cheap":1,"cleanup":1,"cli":1,"closest":1,"comparing":1,"comparison":1,"computes":1,"content":1,"count":1,"creating":1,"cryptographic":1,"dead":1,"default":2,"differing":1,"distance":4,"does":1,"duplicate":3,"duplicates":2,"e":1,"embedding":1,"even":1,"every":2,"example":1,"excellent":1,"false":1,"feed":1,"few":1,"find":1,"finding":1,"finds":1,"fingerprint":1,"fingerprints":1,"finishes":1,"fixed":1,"flag":1,"flags":1,"flipping":1,"flooding":1,"for":4,"force":1,"from":2,"full":1,"g":1,"get":1,"gives":1,"graph":1,"half":1,"hamming":3,"hash":4,"hashes":1,"identical":1,"if":2,"in":3,"index":1,"input":1,"is":5,"ish":1,"it":10,"json":1,"just":1,"length":1,"letter":1,"liner":1,"link":1,"linked":1,"linking":1,"links":3,"locality":1,"long":1,"lower":2,"makes":1,"matches":2,"matter":1,"max":2,"meant":2,"merge":1,"millisecond":1,"model":1,"month":1,"more":1,"most":1,"n":1,"name":2,"names":2,"near":3,"nearly":1,"network":1,"new":1,"no":4,"not":2,"number":1,"obvious":1,"of":5,"off":1,"on":3,"once":1,"one":3,"ones":2,"only":3,"originals":1,"orphans":1,"out":1,"output":3,"over":3,"overview":1,"page":8,"pages":6,"pair":1,"per":1,"periodic":1,"positives":1,"probably":1,"produce":1,"query":1,"raise":1,"recognise":1,"recovering":2,"related":1,"remember":1,"report":1,"results":1,"returns":1,"run":2,"same":1,"says":1,"scans":1,"score":1,"scrambles":1,"searching":2,"see":2,"sees":1,"sensitive":1,"share":1,"sibling":1,"simhash":5,"similar":5,"similarity":2,"slightly":1,"smaller":1,"spawn":1,"spelling":1,"splits":1,"spotting":1,"stand":1,"storing":1,"stricter":1,"string":2,"strings":1,"such":2,"suggesting":2,"suitable":1,"sunday":1,"surfaces":1,"sweep":1,"tail":1,"tens":1,"text":1,"that":2,"the":18,"them":1,"then":1,"there":1,"these":1,"they":1,"things":1,"though":1,"thousands":1,"three":2,"threshold":1,"time":2,"titled":2,"titles":1,"to":11,"top":1,"topic":1,"tradeoff":1,"try":1,"tuned":1,"two":1,"type":1,"typos":3,"under":1,"unlike":1,"use":3,"uses":1,"vaguely":1,"vault":1,"vaults":1,"want":1,"weekly":1,"what":1,"when":2,"where":1,"which":1,"while":1,"whole":1,"whose":1,"will":2,"without":2,"would":1,"writing":1,"you":8,"your":1,"zero":1,"zetl":2}},{"dl":334,"n":"Searching","s":"finding/searching","secs":[{"h":"Searching","l":6,"t":"`zetl search` scans your vault for a string and prints the matches, with context. It is deliberately small: a fast text finder for when you remember a phrase but not which note it lives in. No regex engine, no query DSL. If you want regex, pipe the raw index through `rg`. What zetl gives you instead is graph-aware narrowing."},{"h":"The two flags you will actually use","l":16,"t":"| Flag | What it does | |------|--------------| | `--near \"Zettelkasten Method\"` | Only match pages within `--depth` hops of that page | | `--case-sensitive` | Exact case, no folding | `--near` is the one most people miss. A keyword search over 4,000 notes gives you noise; a search restricted to \"notes that cite *Zettelkasten Method* or one link away\" gives you signal. Other useful flags: - `--path \"journal/**\"` — glob filter on file paths. - `--context 120` — widen the snippet from the default 40 characters. - `--limit 200` — default is 50; raise for grep-replace workflows. See [[CLI Overview]] for the full list."},{"h":"JSON when piped","l":41,"t":"When stdout is a pipe or redirect, `zetl search` auto-switches to JSON. This is the contract that lets you chain searches into scripts: You can force JSON with `--json` on a TTY, and force the human table with `-f table`."},{"h":"Example: research workflow","l":52,"t":"You are writing a paper and want every note you tagged `active` that mentions CRDTs. Combine search with frontmatter filtering in jq: Two lines, no database. The `--json` payload includes the page name, the match snippet, path, line number, and parsed frontmatter — enough to build any filter you want outside zetl."},{"h":"Semantic search (optional)","l":65,"t":"If you built zetl with `--features semantic`, two extra flags appear: - `--semantic` — pure vector search, ranked by cosine similarity. Good for \"find notes about this *idea*, even if they use different words.\" - `--hybrid` — reciprocal-rank fusion of BM25 and vector results. This is usually what you want: the precision of keyword search with the recall of embeddings. Without the feature flag, both options error out with a build hint. See [[Installation]]."},{"h":"Time travel","l":78,"t":"`--at \"3 days ago\"` runs the search against a historical snapshot. Requires `--features history` and a vault you have been snapshotting with `zetl watch`. See [[Time Travel]]."},{"h":"Related","l":82,"t":"- [[Backlinks]] - [[Similar Pages]] - [[Following Links]] - [[CLI Overview]]"}],"tf":{"000":1,"4":1,"40":1,"50":1,"a":11,"about":1,"actually":1,"against":1,"and":6,"any":1,"appear":1,"are":1,"auto":1,"aware":1,"away":1,"backlinks":1,"been":1,"bm25":1,"both":1,"build":2,"built":1,"but":1,"by":1,"can":1,"case":1,"chain":1,"characters":1,"cite":1,"cli":2,"combine":1,"context":1,"contract":1,"cosine":1,"crdts":1,"database":1,"default":2,"deliberately":1,"different":1,"does":1,"dsl":1,"embeddings":1,"engine":1,"enough":1,"error":1,"even":1,"every":1,"exact":1,"example":1,"extra":1,"fast":1,"feature":1,"file":1,"filter":2,"filtering":1,"find":1,"finder":1,"flag":2,"flags":3,"folding":1,"following":1,"for":5,"force":2,"from":1,"frontmatter":2,"full":1,"fusion":1,"gives":3,"glob":1,"good":1,"graph":1,"grep":1,"have":1,"hint":1,"historical":1,"hops":1,"human":1,"idea":1,"if":3,"in":2,"includes":1,"index":1,"installation":1,"instead":1,"into":1,"is":7,"it":3,"jq":1,"json":3,"keyword":2,"lets":1,"line":1,"lines":1,"link":1,"links":1,"list":1,"lives":1,"match":2,"matches":1,"mentions":1,"method":1,"miss":1,"most":1,"name":1,"narrowing":1,"no":4,"noise":1,"not":1,"note":2,"notes":3,"number":1,"of":4,"on":2,"one":2,"only":1,"optional":1,"options":1,"or":2,"other":1,"out":1,"outside":1,"over":1,"overview":2,"page":2,"pages":2,"paper":1,"parsed":1,"path":1,"paths":1,"payload":1,"people":1,"phrase":1,"pipe":2,"piped":1,"precision":1,"prints":1,"pure":1,"query":1,"raise":1,"rank":1,"ranked":1,"raw":1,"recall":1,"reciprocal":1,"redirect":1,"regex":2,"related":1,"remember":1,"replace":1,"requires":1,"research":1,"restricted":1,"results":1,"runs":1,"scans":1,"scripts":1,"search":7,"searches":1,"searching":1,"see":3,"semantic":1,"signal":1,"similar":1,"similarity":1,"small":1,"snapshot":1,"snapshotting":1,"snippet":2,"stdout":1,"string":1,"switches":1,"table":1,"tagged":1,"text":1,"that":4,"the":16,"they":1,"this":3,"through":1,"time":2,"to":3,"travel":2,"tty":1,"two":3,"use":2,"useful":1,"usually":1,"vault":2,"vector":2,"want":4,"what":3,"when":3,"which":1,"widen":1,"will":1,"with":8,"within":1,"without":1,"words":1,"workflow":1,"workflows":1,"writing":1,"you":14,"your":1,"zetl":3,"zettelkasten":1}},{"dl":593,"n":"Proof Trees","s":"reasoning/proof-trees","secs":[{"h":"Proof Trees","l":6,"t":"`zetl reason explain <literal>` shows **why** a conclusion holds. The output is a proof tree: the rules that fired, the facts they rested on, and any defeaters that were considered. This page is about when and how to read one. > **Requires `--features reason` at install.** See [[Installation]]."},{"h":"What a proof tree is","l":12,"t":"When zetl concludes that some literal holds (or doesn't), it found — or failed to find — a chain of rules and facts that lead to that literal. A proof tree is that chain laid out as text: - The **root** is the literal you asked about. - Each **node** is a rule that fired, labelled with the rule's name. - Each **leaf** is a fact (`given`) or a dead end (a body literal that couldn't be proved). - **Defeaters** that were considered — and either won or were overridden — appear alongside the main chain. Every node carries **provenance**: the source file, line number, and page name where that rule or fact was written. You can walk from any conclusion back to the exact prose that originated it."},{"h":"The command","l":23,"t":"By default the output is JSON. Three other formats are available via `--as`: | `--as`        | What you get | |---------------|--------------| | `json`        | Structured proof tree (default) | | `table`       | A tabular view of nodes, rules, and sources | | `natural`     | English prose: \"The literal X is defeasibly provable because …\" | | `dot`         | Graphviz DOT source — pipe to `dot -Tpng` for a diagram | There is also `--depth N` (default 10) which caps how deep the tree goes. Unbounded trees in large vaults can be overwhelming; 4 or 5 is usually enough for day-to-day use."},{"h":"The natural-language form","l":40,"t":"`--as natural` is the one to use when you want a sentence you can paste into Slack: Output looks like: No formal notation, no symbols — just the chain of reasoning written out."},{"h":"The diagram form","l":60,"t":"`--as dot` emits Graphviz source. Good for architectural reviews and posters: The resulting diagram has one node per rule and fact, with arrows showing how conclusions flow. Defeaters appear as dotted edges."},{"h":"When to read one","l":71,"t":"The usual trigger is surprise. You ran `zetl reason status`, you saw a conclusion that doesn't match what you expected, and you need to know why. A few concrete cases: - **\"Why does zetl say the release candidate is ready?\"** You expected one more review. `explain` tells you which facts made the readiness rule fire — including, possibly, an outdated `given` still sitting in an old meeting note. - **\"Who signed off on docs-updated?\"** The rule fired, which means somewhere a `given docs-updated` was asserted. The proof tree names the file and line. Go look; it might be three months old. - **\"Why *isn't* the build-blocked literal holding?\"** When you expected a negative conclusion but got silence. The tree shows which body literals failed to be proved, pointing you at the missing premise. When the conclusion is negative and you want the **failure** analysis — what's missing, what defeated what — reach for `zetl reason why-not <literal>` instead. It is the explicit counterpart to `explain`, and it lists candidate rules with their blockers: For each rule that could have produced the conclusion, `why-not` shows which body literals were unprovable or which defeater fired."},{"h":"Cross-referencing with provenance","l":87,"t":"`reason explain` is the deep-dive view of a single conclusion. [[Running Queries|`reason provenance`]] is the shallower, graph-oriented view — \"what pages contributed?\" rather than \"what tree of rules fired?\". Use them together:"},{"h":"A worked pattern","l":99,"t":"A habit worth adopting: whenever `zetl reason status` tells you something you didn't expect, pipe the literal straight into `explain --as natural`: Nine times out of ten, the explanation points at a stale `given` or a missing `(prefer ...)` — the kind of thing you fix in thirty seconds once you can see it."},{"h":"Related","l":111,"t":"- [[Running Queries]] - [[Writing SPL]] - [[What-if and Abduction]] - [[What is Defeasible Reasoning]]"}],"tf":{"10":1,"4":1,"5":1,"a":21,"abduction":1,"about":2,"adopting":1,"alongside":1,"also":1,"an":2,"analysis":1,"and":13,"any":2,"appear":2,"architectural":1,"are":1,"arrows":1,"as":2,"asked":1,"asserted":1,"at":3,"available":1,"back":1,"be":4,"because":1,"blocked":1,"blockers":1,"body":3,"build":1,"but":1,"by":1,"can":4,"candidate":2,"caps":1,"carries":1,"cases":1,"chain":4,"command":1,"concludes":1,"conclusion":7,"conclusions":1,"concrete":1,"considered":2,"contributed":1,"could":1,"couldn":1,"counterpart":1,"cross":1,"day":2,"dead":1,"deep":2,"default":3,"defeasible":1,"defeasibly":1,"defeated":1,"defeater":1,"defeaters":3,"diagram":3,"didn":1,"dive":1,"docs":1,"does":1,"doesn":2,"dot":1,"dotted":1,"each":3,"edges":1,"either":1,"emits":1,"end":1,"english":1,"enough":1,"every":1,"exact":1,"expect":1,"expected":3,"explanation":1,"explicit":1,"fact":3,"facts":3,"failed":2,"failure":1,"few":1,"file":2,"find":1,"fire":1,"fired":5,"fix":1,"flow":1,"for":5,"form":2,"formal":1,"formats":1,"found":1,"from":1,"get":1,"go":1,"goes":1,"good":1,"got":1,"graph":1,"graphviz":2,"habit":1,"has":1,"have":1,"holding":1,"holds":2,"how":3,"if":1,"in":3,"including":1,"install":1,"installation":1,"instead":1,"into":2,"is":19,"isn":1,"it":6,"json":1,"just":1,"kind":1,"know":1,"labelled":1,"laid":1,"language":1,"large":1,"lead":1,"leaf":1,"like":1,"line":2,"lists":1,"literal":7,"literals":2,"look":1,"looks":1,"made":1,"main":1,"match":1,"means":1,"meeting":1,"might":1,"missing":3,"months":1,"more":1,"name":2,"names":1,"natural":1,"need":1,"negative":2,"nine":1,"no":2,"node":3,"nodes":1,"notation":1,"note":1,"number":1,"of":7,"off":1,"old":2,"on":2,"once":1,"one":5,"or":8,"oriented":1,"originated":1,"other":1,"out":3,"outdated":1,"output":3,"overridden":1,"overwhelming":1,"page":2,"pages":1,"paste":1,"pattern":1,"per":1,"pipe":2,"pointing":1,"points":1,"possibly":1,"posters":1,"premise":1,"produced":1,"proof":6,"prose":2,"provable":1,"proved":2,"provenance":2,"queries":2,"ran":1,"rather":1,"reach":1,"read":2,"readiness":1,"ready":1,"reasoning":2,"referencing":1,"related":1,"release":1,"requires":1,"rested":1,"resulting":1,"review":1,"reviews":1,"root":1,"rule":7,"rules":5,"running":2,"s":2,"saw":1,"say":1,"seconds":1,"see":2,"sentence":1,"shallower":1,"showing":1,"shows":3,"signed":1,"silence":1,"single":1,"sitting":1,"slack":1,"some":1,"something":1,"somewhere":1,"source":3,"sources":1,"spl":1,"stale":1,"still":1,"straight":1,"structured":1,"surprise":1,"symbols":1,"t":5,"tabular":1,"tells":2,"ten":1,"text":1,"than":1,"that":13,"the":36,"their":1,"them":1,"there":1,"they":1,"thing":1,"thirty":1,"this":1,"three":2,"times":1,"to":11,"together":1,"tree":8,"trees":2,"trigger":1,"unbounded":1,"unprovable":1,"updated":1,"use":3,"usual":1,"usually":1,"vaults":1,"via":1,"view":3,"walk":1,"want":2,"was":2,"were":4,"what":10,"when":6,"whenever":1,"where":1,"which":6,"who":1,"why":4,"with":4,"won":1,"worked":1,"worth":1,"writing":1,"written":2,"x":1,"you":18,"zetl":2}},{"dl":657,"n":"What is Defeasible Reasoning","s":"reasoning/what-is-defeasible-reasoning","secs":[{"h":"What is Defeasible Reasoning","l":6,"t":"A short primer on the kind of logic zetl uses when you ask it to reason over your notes. No symbols, no formal notation — just an explanation of why this flavour of logic suits knowledge work. > **Requires `--features reason` at install.** See [[Installation]]."},{"h":"Classical logic: everything is final","l":12,"t":"In classical logic, facts combine and the conclusion is permanent. If you write down that every bird flies, and that Tweety is a bird, then Tweety flies. Forever. Adding new information can only produce new conclusions — it can never retract one you already made. That rule works beautifully inside a maths textbook. It breaks almost immediately when you try to write down the actual state of a research project, a release plan, or a set of beliefs you formed over six months of reading. Real knowledge is provisional. You revise it. You hedge. You say \"normally X, but not when Y\"."},{"h":"Defeasible logic: conclusions have strength","l":18,"t":"Defeasible reasoning lets you write rules that are **normally** true but **can be defeated** by stronger evidence. In a project-tracking vault you might write: - *Normally, a release candidate is ready if core features are done, docs are written, and tests pass.* - *Except when there is an outstanding critical bug.* Both rules live in the theory at the same time. When zetl reasons over them, the defeater wins if its conditions hold — the release is blocked. If the bug is later fixed, the block lifts and the conclusion flips. You never rewrote the rules; you just added and removed facts. This is exactly how thinking about ongoing work feels. \"We're on track — unless the client pushes back on the design.\" \"The proposal is done — except we still need a legal review.\" You already reason this way. zetl just gives you a notation for it."},{"h":"The four conclusion tags","l":29,"t":"Every literal (a thing that might or might not hold) ends up with one of four labels: | Tag  | Meaning                   | Plain English | |------|---------------------------|---------------| | `+D` | Definitely provable       | The strict rules force this — there is no way around it. | | `-D` | Definitely not provable   | Strict rules actively rule it out. | | `+d` | Defeasibly provable       | Supported by the evidence you have; no stronger defeater is active. | | `-d` | Defeasibly not provable   | Either nothing supports it, or a defeater blocks the supporting rule. | Think of `+D`/`-D` as the **hard** layer — things you are certain about, or things that are mathematically excluded. Think of `+d`/`-d` as the **soft** layer — your current best belief, open to revision as new facts arrive. A well-formed reasoning vault uses `always` rules (which produce `+D`/`-D`) sparingly — for genuine invariants — and `normally` rules (which produce `+d`/`-d`) for almost everything else. Knowledge work is defeasible by default."},{"h":"Why this fits notes better than classical logic","l":44,"t":"Three reasons: 1. **Beliefs revise.** Your notes are a record of thinking that changes over time. A logic that can downgrade yesterday's conclusion without you rewriting it is a better match for that record. 2. **Rules have strengths.** When two pages disagree — a design doc says \"use Redis\", a later benchmark says \"use SQLite\" — you want the newer, stronger claim to win without silencing the older one. Defeasible logic gives you preference relations for exactly this. 3. **Tentative answers are legitimate.** Saying \"on current evidence, probably yes, but I'd want to check\" is how humans actually reason. The `+d` tag is zetl's way of saying the same thing back to you."},{"h":"What this enables","l":52,"t":"Once rules and facts live inside your vault, zetl can do things that static notes cannot: - Tell you *why* a conclusion holds, tracing it back to the page and line that contributed (see [[Proof Trees]]). - Flag contradictions before you notice them (see [[Running Queries]]). - Answer hypothetical questions — \"what would I need to add to make this project ready?\" (see [[What-if and Abduction]]). A vault with reasoning rules doubles as a living checklist, a set of architectural commitments, or a decision journal — queryable from the command line."},{"h":"Related","l":62,"t":"- [[Writing SPL]] - [[Running Queries]] - [[What is SPL]] - [[Proof Trees]]"}],"tf":{"1":1,"2":1,"3":1,"a":23,"abduction":1,"about":2,"active":1,"actively":1,"actual":1,"actually":1,"add":1,"added":1,"adding":1,"almost":2,"already":2,"an":2,"and":9,"answer":1,"answers":1,"architectural":1,"are":7,"around":1,"arrive":1,"as":4,"ask":1,"at":2,"back":3,"be":1,"beautifully":1,"before":1,"belief":1,"beliefs":2,"benchmark":1,"best":1,"better":2,"bird":2,"block":1,"blocked":1,"blocks":1,"both":1,"breaks":1,"bug":2,"but":3,"by":3,"can":5,"candidate":1,"cannot":1,"certain":1,"changes":1,"check":1,"checklist":1,"claim":1,"classical":3,"client":1,"combine":1,"command":1,"commitments":1,"conclusion":5,"conclusions":2,"conditions":1,"contradictions":1,"contributed":1,"core":1,"critical":1,"current":2,"d":1,"decision":1,"default":1,"defeasible":5,"defeasibly":2,"defeated":1,"defeater":3,"definitely":2,"design":2,"disagree":1,"do":1,"doc":1,"docs":1,"done":2,"doubles":1,"down":2,"downgrade":1,"either":1,"else":1,"enables":1,"ends":1,"english":1,"every":2,"everything":2,"evidence":3,"exactly":2,"except":2,"excluded":1,"explanation":1,"facts":4,"features":1,"feels":1,"final":1,"fits":1,"fixed":1,"flag":1,"flavour":1,"flies":2,"flips":1,"for":5,"force":1,"forever":1,"formal":1,"formed":2,"four":2,"from":1,"genuine":1,"gives":2,"hard":1,"have":3,"hedge":1,"hold":2,"holds":1,"how":2,"humans":1,"hypothetical":1,"i":2,"if":5,"immediately":1,"in":3,"information":1,"inside":2,"install":1,"installation":1,"invariants":1,"is":18,"it":10,"its":1,"journal":1,"just":3,"kind":1,"knowledge":3,"labels":1,"later":2,"layer":2,"legal":1,"legitimate":1,"lets":1,"lifts":1,"line":2,"literal":1,"live":2,"living":1,"logic":8,"made":1,"make":1,"match":1,"mathematically":1,"maths":1,"meaning":1,"might":3,"months":1,"need":2,"never":2,"new":3,"newer":1,"no":4,"normally":3,"not":4,"notation":2,"notes":4,"nothing":1,"notice":1,"of":12,"older":1,"on":4,"once":1,"one":3,"ongoing":1,"only":1,"open":1,"or":5,"out":1,"outstanding":1,"over":4,"page":1,"pages":1,"pass":1,"permanent":1,"plain":1,"plan":1,"preference":1,"primer":1,"probably":1,"produce":3,"project":3,"proof":2,"proposal":1,"provable":4,"provisional":1,"pushes":1,"queries":2,"queryable":1,"questions":1,"re":1,"reading":1,"ready":2,"real":1,"reason":3,"reasoning":4,"reasons":2,"record":2,"redis":1,"related":1,"relations":1,"release":3,"removed":1,"requires":1,"research":1,"retract":1,"review":1,"revise":2,"revision":1,"rewriting":1,"rewrote":1,"rule":3,"rules":10,"running":2,"s":2,"same":2,"say":1,"saying":2,"says":2,"see":4,"set":2,"short":1,"silencing":1,"six":1,"soft":1,"sparingly":1,"spl":2,"sqlite":1,"state":1,"static":1,"still":1,"strength":1,"strengths":1,"strict":2,"stronger":3,"suits":1,"supported":1,"supporting":1,"supports":1,"symbols":1,"tag":2,"tags":1,"tell":1,"tentative":1,"tests":1,"textbook":1,"than":1,"that":11,"the":26,"them":2,"then":1,"theory":1,"there":2,"thing":2,"things":3,"think":2,"thinking":2,"this":8,"three":1,"time":2,"to":9,"tracing":1,"track":1,"tracking":1,"trees":2,"true":1,"try":1,"tweety":2,"two":1,"unless":1,"up":1,"use":2,"uses":2,"vault":4,"want":2,"way":3,"we":2,"well":1,"what":5,"when":6,"which":2,"why":3,"win":1,"wins":1,"with":2,"without":2,"work":3,"works":1,"would":1,"write":4,"writing":1,"written":1,"x":1,"y":1,"yes":1,"yesterday":1,"you":22,"your":4,"zetl":5}},{"dl":543,"n":"Running Queries","s":"reasoning/running-queries","secs":[{"h":"Running Queries","l":6,"t":"Once your vault contains [[Writing SPL|some SPL]], `zetl reason` is how you ask questions of it. This page walks the everyday command surface: status, conflicts, export, and provenance. > **Requires `--features reason` at install.** See [[Installation]]."},{"h":"`reason status` — the default view","l":12,"t":"`zetl reason status` prints every conclusion the combined theory produces, tagged with `+D`, `-D`, `+d`, or `-d` (see [[What is Defeasible Reasoning]] for what those mean). In a TTY you get a table. Piped or redirected, you get JSON — same data, machine-readable."},{"h":"Filters","l":23,"t":"Status output gets long fast. A few flags keep it manageable: | Flag                      | Shows | |---------------------------|-------| | `--positive`              | Only `+D` and `+d` conclusions | | `--negative`              | Only `-D` and `-d` conclusions | | `--definite`              | Only `+D` and `-D` (the strict layer) | | `--defeasible`            | Only `+d` and `-d` (the soft layer) | | `--literal \"release*\"`    | Wildcard filter on the literal name (`*` and `?` supported) | Combine them. \"What are all the things my Acme project is currently defeasibly committed to?\" becomes:"},{"h":"`reason conflicts` — the contradictions report","l":41,"t":"When two rules conclude opposite things about the same literal — and no `(prefer ...)` picks a winner — zetl calls that an **unresolved conflict**. `reason conflicts` lists them: Useful flags: - `--suggest` — for each conflict, propose a preference that would resolve it. - `--fail-on-conflicts` — exit with code 1 if any conflicts exist. Drop this into CI to gate merges on a contradiction-free theory. A fresh vault almost always has one or two conflicts — someone wrote a defeater but forgot the preference. The report points at both competing rules by file and line."},{"h":"`reason export` — the whole theory","l":61,"t":"`reason export` dumps the combined theory (every fact, every rule, every preference). Two shapes: The SPL form is handy for debugging \"where is this rule actually coming from?\" — each rule is prefixed with a comment naming its source file and line. The JSON form is what you pipe to an agent or a downstream tool."},{"h":"`reason provenance <literal>` — the sources","l":73,"t":"`reason provenance` answers *which files and line numbers contributed to this conclusion?* It cross-references the proof sources with the link graph so you can see the pages involved alongside the raw file paths. Output includes: - Every fact whose literal appears in the proof. - Every rule that fired, with its source page and line. - The cross-referenced list of pages that contributed — the same pages you'd see as backlinks in a graph view. This is the command to run when you want to say in a team meeting, \"here's exactly where in the vault this conclusion came from\" without hunting through files."},{"h":"Cross-referencing with the link graph","l":89,"t":"Reasoning is not separate from your wikilink graph — the same pages power both. Two graph commands take a `--with-conclusions` flag that overlays reasoning state onto their output: This prints every page that links into `Acme Website` alongside the SPL conclusions each of those pages contributes. Answer: *which notes are actually asserting things that feed this project's readiness?* See [[Backlinks]] for the base command."},{"h":"Output formatting rules","l":101,"t":"All `reason` subcommands follow the same two rules as the rest of zetl: 1. **In a terminal**, you get a table. 2. **Piped or redirected**, you get JSON. Force one with the global `--json` flag or `-f table` — they work before or after the subcommand. Errors go to stderr so piped stdout stays valid JSON. See [[CLI Overview]] for the house rules."},{"h":"Time travel","l":110,"t":"Every `reason` subcommand accepts `--at`. With `--features history` installed, you can ask what the theory concluded at any past point: Useful for post-mortems and decision audits. See [[Time Travel]]."},{"h":"Related","l":121,"t":"- [[Writing SPL]] - [[Proof Trees]] - [[What-if and Abduction]] - [[CLI Overview]] - [[Time Travel]]"}],"tf":{"1":2,"2":1,"a":15,"abduction":1,"about":1,"accepts":1,"acme":1,"actually":2,"after":1,"agent":1,"all":2,"almost":1,"alongside":2,"always":1,"an":2,"and":13,"answer":1,"answers":1,"any":2,"appears":1,"are":2,"as":2,"ask":2,"asserting":1,"at":3,"audits":1,"backlinks":2,"base":1,"becomes":1,"before":1,"both":2,"but":1,"by":1,"calls":1,"came":1,"can":2,"ci":1,"cli":2,"code":1,"combine":1,"combined":2,"coming":1,"command":3,"commands":1,"comment":1,"committed":1,"competing":1,"conclude":1,"concluded":1,"conclusion":3,"conclusions":3,"conflict":2,"conflicts":3,"contains":1,"contradiction":1,"contradictions":1,"contributed":2,"contributes":1,"cross":3,"currently":1,"d":1,"data":1,"debugging":1,"decision":1,"default":1,"defeasible":1,"defeasibly":1,"defeater":1,"downstream":1,"drop":1,"dumps":1,"each":3,"errors":1,"every":8,"everyday":1,"exactly":1,"exist":1,"exit":1,"export":1,"fact":2,"fast":1,"feed":1,"few":1,"file":3,"files":2,"filter":1,"filters":1,"fired":1,"flag":3,"flags":2,"follow":1,"for":6,"force":1,"forgot":1,"form":2,"formatting":1,"free":1,"fresh":1,"from":3,"gate":1,"get":4,"gets":1,"global":1,"go":1,"graph":5,"handy":1,"has":1,"here":1,"house":1,"how":1,"hunting":1,"if":2,"in":6,"includes":1,"install":1,"installation":1,"installed":1,"into":2,"involved":1,"is":9,"it":4,"its":2,"json":4,"keep":1,"layer":2,"line":4,"link":2,"links":1,"list":1,"lists":1,"literal":3,"long":1,"machine":1,"manageable":1,"mean":1,"meeting":1,"merges":1,"mortems":1,"my":1,"name":1,"naming":1,"no":1,"not":1,"notes":1,"numbers":1,"of":4,"on":2,"once":1,"one":2,"only":4,"onto":1,"opposite":1,"or":7,"output":4,"overlays":1,"overview":2,"page":3,"pages":5,"past":1,"paths":1,"picks":1,"pipe":1,"piped":3,"point":1,"points":1,"post":1,"power":1,"preference":3,"prefixed":1,"prints":2,"produces":1,"project":2,"proof":3,"propose":1,"provenance":1,"queries":1,"questions":1,"raw":1,"readable":1,"readiness":1,"reasoning":3,"redirected":2,"referenced":1,"references":1,"referencing":1,"related":1,"report":2,"requires":1,"resolve":1,"rest":1,"rule":4,"rules":5,"run":1,"running":1,"s":2,"same":5,"say":1,"see":7,"separate":1,"shapes":1,"shows":1,"so":2,"soft":1,"some":1,"someone":1,"source":2,"sources":2,"spl":5,"state":1,"status":2,"stays":1,"stderr":1,"stdout":1,"strict":1,"subcommand":2,"subcommands":1,"supported":1,"surface":1,"table":2,"tagged":1,"take":1,"team":1,"terminal":1,"that":7,"the":35,"their":1,"them":2,"theory":5,"they":1,"things":3,"this":8,"those":2,"through":1,"time":3,"to":7,"tool":1,"travel":3,"trees":1,"tty":1,"two":5,"unresolved":1,"useful":2,"valid":1,"vault":3,"view":2,"walks":1,"want":1,"what":6,"when":2,"where":2,"which":2,"whole":1,"whose":1,"wikilink":1,"wildcard":1,"winner":1,"with":8,"without":1,"work":1,"would":1,"writing":2,"wrote":1,"you":10,"your":2,"zetl":2}},{"dl":553,"n":"What-if and Abduction","s":"reasoning/what-if-and-abduction","secs":[{"h":"What-if and Abduction","l":6,"t":"Two commands — `zetl reason what-if` and `zetl reason require` — let you play with the theory without actually editing it. One asks \"what changes if I add this fact?\". The other asks \"what facts would I need to prove this goal?\". Both are indispensable when planning. > **Requires `--features reason` at install.** See [[Installation]]."},{"h":"`reason what-if` — forward hypotheticals","l":12,"t":"`what-if` takes a snippet of SPL, temporarily adds it to the theory, recomputes conclusions, and reports the diff. Your vault files are not touched. Output: every conclusion that changed — literals that newly hold, literals that stopped holding, tags that flipped between `+d` and `-d`. The rest is filtered out."},{"h":"Focus on a specific goal","l":22,"t":"`--goal` narrows the diff to a single literal you care about. Answers the question \"if I add this, does *this* become provable?\":"},{"h":"From a file","l":30,"t":"If you want to try a larger experiment — a whole new rule, several facts — put it in a file and pass it with `--file`: Useful when you are exploring \"what if we relaxed this constraint?\" without committing to the change."},{"h":"Use cases","l":46,"t":"- **Planning.** \"If the client approves the proposal on Monday, are we clear to start?\" — add `(given client-approved)` and look at the diff. - **Gating a decision.** \"The build is red. Would merging this feature make any currently-`+d` conclusion flip to `-d`?\" — add the facts your feature introduces and check. - **Defensive checks.** Before committing a new SPL rule, run `what-if` with it pasted in. If the conclusions diff is bigger than you expected, your rule is too broad."},{"h":"`reason require` — backward abduction","l":52,"t":"`require` is the inverse question: you have a goal in mind, and you want to know **what facts you'd need to add** to make it provable. This is abductive reasoning — inference from goal to premises. zetl returns the minimal sets of missing facts that would complete the derivation. Each set is a separate solution — there may be several ways to prove the goal, via different rules."},{"h":"Example output","l":62,"t":"Read this as a to-do list. Each required fact is something you (or the project) must actually produce; it can't be derived from the rules currently in the vault."},{"h":"Chaining assumptions with `--assume`","l":78,"t":"The `--assume` flag lets you say \"treat these as already given\" and ask what else is still needed: This is the practical planning form: you know some things are already true, you want to know what remains. Chain multiple `--assume` flags for cumulative hypotheticals."},{"h":"Capping results","l":90,"t":"`--max-solutions N` (default 5) caps how many distinct requirement sets are returned. For tightly constrained goals you usually get one; for goals reachable through many alternative rules you may want a higher cap to see all the options."},{"h":"Use cases side by side","l":94,"t":"| Question                                                | Command | |--------------------------------------------------------|---------| | \"What's blocking the release?\"                         | `reason require \"release-candidate\"` | | \"If the client approves, are we good?\"                 | `reason what-if \"(given client-approved)\" --goal \"ready\"` | | \"Would adding this rule change any current conclusion?\"| `reason what-if --file new-rule.spl` | | \"What single fact is closest to getting us unblocked?\" | `reason require \"ready-to-ship\"` | `require` is the command to run at the start of a planning session — it turns a vague goal into a concrete list of facts to produce. `what-if` is the command to run in the middle, as you weigh one decision at a time."},{"h":"Time travel","l":105,"t":"Both accept `--at` when `--features history` is installed. `require --at \"last monday\"` tells you what the required facts *were* at a past point, which is useful for decision retrospectives: \"what did we think it would take to ship, before we knew about the legal issue?\". See [[Time Travel]]."},{"h":"Related","l":109,"t":"- [[Running Queries]] - [[Proof Trees]] - [[Writing SPL]] - [[What is Defeasible Reasoning]]"}],"tf":{"5":1,"a":18,"abduction":2,"abductive":1,"about":2,"accept":1,"actually":2,"add":5,"adding":1,"adds":1,"all":1,"already":2,"alternative":1,"and":9,"answers":1,"any":2,"approves":2,"are":7,"as":3,"ask":1,"asks":2,"assumptions":1,"at":5,"backward":1,"be":2,"become":1,"before":2,"between":1,"bigger":1,"blocking":1,"both":2,"broad":1,"build":1,"by":1,"can":1,"cap":1,"capping":1,"caps":1,"care":1,"cases":2,"chain":1,"chaining":1,"change":2,"changed":1,"changes":1,"check":1,"checks":1,"clear":1,"client":2,"closest":1,"command":3,"commands":1,"committing":2,"complete":1,"conclusion":3,"conclusions":2,"concrete":1,"constrained":1,"constraint":1,"cumulative":1,"current":1,"currently":2,"d":1,"decision":3,"default":1,"defeasible":1,"defensive":1,"derivation":1,"derived":1,"did":1,"diff":4,"different":1,"distinct":1,"do":1,"does":1,"each":2,"editing":1,"else":1,"every":1,"example":1,"expected":1,"experiment":1,"exploring":1,"fact":3,"facts":7,"feature":2,"file":2,"files":1,"filtered":1,"flag":1,"flags":1,"flip":1,"flipped":1,"focus":1,"for":4,"form":1,"forward":1,"from":3,"gating":1,"get":1,"getting":1,"given":1,"goal":6,"goals":2,"good":1,"have":1,"higher":1,"hold":1,"holding":1,"how":1,"hypotheticals":2,"i":3,"if":8,"in":5,"indispensable":1,"inference":1,"install":1,"installation":1,"installed":1,"into":1,"introduces":1,"inverse":1,"is":16,"issue":1,"it":9,"knew":1,"know":3,"larger":1,"legal":1,"let":1,"lets":1,"list":2,"literal":1,"literals":2,"look":1,"make":2,"many":2,"may":2,"merging":1,"middle":1,"mind":1,"minimal":1,"missing":1,"monday":1,"multiple":1,"must":1,"narrows":1,"need":2,"needed":1,"new":2,"newly":1,"not":1,"of":4,"on":2,"one":3,"options":1,"or":1,"other":1,"out":1,"output":2,"pass":1,"past":1,"pasted":1,"planning":4,"play":1,"point":1,"practical":1,"premises":1,"produce":2,"project":1,"proof":1,"proposal":1,"provable":2,"prove":2,"put":1,"queries":1,"question":3,"reachable":1,"read":1,"reasoning":2,"recomputes":1,"red":1,"related":1,"relaxed":1,"release":1,"remains":1,"reports":1,"required":2,"requirement":1,"requires":1,"rest":1,"results":1,"retrospectives":1,"returned":1,"returns":1,"rule":4,"rules":3,"run":3,"running":1,"s":1,"say":1,"see":3,"separate":1,"session":1,"set":1,"sets":2,"several":2,"ship":1,"side":2,"single":2,"snippet":1,"solution":1,"some":1,"something":1,"specific":1,"spl":3,"start":2,"still":1,"stopped":1,"t":1,"tags":1,"take":1,"takes":1,"tells":1,"temporarily":1,"than":1,"that":5,"the":32,"theory":2,"there":1,"these":1,"things":1,"think":1,"this":10,"through":1,"tightly":1,"time":3,"to":20,"too":1,"touched":1,"travel":2,"treat":1,"trees":1,"true":1,"try":1,"turns":1,"two":1,"unblocked":1,"us":1,"use":2,"useful":2,"usually":1,"vague":1,"vault":2,"via":1,"want":4,"ways":1,"we":5,"weigh":1,"were":1,"what":12,"when":3,"which":1,"whole":1,"with":4,"without":2,"would":5,"writing":1,"you":16,"your":3,"zetl":1}},{"dl":605,"n":"Writing SPL","s":"reasoning/writing-spl","secs":[{"h":"Writing SPL","l":6,"t":"Spindle Lisp (SPL) is the small language zetl reads when you ask it to reason over your vault. This page walks through the syntax by example — no formal grammar, just enough to start writing useful theories. > **Requires `--features reason` at install.** See [[Installation]]."},{"h":"Where SPL goes","l":12,"t":"SPL lives in two places: 1. **Fenced code blocks** inside any Markdown file, tagged `spl`. 2. **Standalone `.spl` files** anywhere in the vault. (given core-features-done) (given docs-drafted) zetl merges every `spl` block and every `.spl` file across the vault into **one combined theory** before reasoning. A fact stated on one page can feed a rule defined on another. This is the point — rules and facts can live next to the prose that motivated them."},{"h":"Facts","l":32,"t":"A fact is something you assert to be true. Use `(given ...)`: One fact per line is conventional. The thing you are asserting (`project-approved`) is called a **literal**. Literals are lower-case identifiers with dashes — zetl does not care about the particular word you pick, only that you spell it the same way everywhere. To state the absence of something, prefix with `~` or write `(not ...)`:"},{"h":"Rules: normally and always","l":50,"t":"Rules say *if the body holds, the head holds*. Two flavours: - `normally` — a defeasible rule. Holds by default, but can be defeated by a stronger rule or a defeater. Produces `+d` / `-d` conclusions. - `always` — a strict rule. Cannot be defeated. Produces `+D` / `-D` conclusions. The first token after `normally`/`always` is the rule's **label** — a short name you can refer to later. Labels are optional but helpful: proof trees and conflict reports use them to point back at the exact rule. The body can be a single literal or an `(and ...)` of several. An implicit \"all of these must hold\" binds the conjunction."},{"h":"Defeaters","l":80,"t":"A defeater blocks a conclusion without asserting the opposite. Useful for exceptions: If `open-critical-bug` is true, any defeasible rule concluding `ready-to-ship` is blocked. This is how you write \"normally yes, **except** when …\" without tangling up your main rules."},{"h":"Preferences","l":94,"t":"When two rules compete — one says `ready`, another says `not ready` — zetl flags the conflict. To resolve it, declare which rule wins: Reads: \"prefer `d-critical-bug` over `r-ready-to-start`\". The critical-bug defeater trumps the normal readiness rule."},{"h":"Comments","l":104,"t":"Lines starting with `;` are ignored:"},{"h":"Modal operators","l":113,"t":"SPL has three modal operators that wrap a literal to express permission, obligation, or prohibition: | Operator | Meaning | Example | |----------|---------|---------| | `(may X)` | X is permitted | `(may (edit ?user ?page))` | | `(must X)` | X is obligatory | `(must (review ?document))` | | `(forbidden X)` | X is forbidden | `(forbidden (publish ?draft))` | Use them as the head of a rule: They compose with defeasible reasoning — a narrower `forbidden` rule can defeat a broader `may` rule via `(prefer ...)`. This is how [[Access Control]] expresses policy like *\"editors can edit research, **except** in `research/private/`.\"*"},{"h":"Learning more","l":135,"t":"SPL is implemented by **[spindle-rust](https://codeberg.org/anuna/spindle-rust)**, which ships a formal reference: grammar, every keyword, temporal operators, arithmetic constraints, trust-weighted claims. If you're writing anything non-trivial, open its docs alongside this page."},{"h":"A realistic vault example","l":139,"t":"Imagine a project-tracking vault. You have a page per project, plus a `theories/` folder with the shared rules. In `projects/Acme Website.md`: (given acme-approved) (given acme-has-owner) (given acme-budget-signed-off) In `theories/project-readiness.spl`: Later, in `meetings/2026-03-15 Acme Kickoff.md`: (given acme-legal-pending) Run `zetl reason status --literal \"acme*\"` and you'll see `acme-ready-to-start` go from `+d` to `-d` the moment the meeting note is saved. Remove the `given` once legal clears it, and the conclusion flips back. The rules never moved — only the facts."},{"h":"A few habits that pay off","l":183,"t":"- **Prefix literals by project or theme.** `acme-approved` reads better than `approved`, and it prevents collisions as the vault grows. - **Keep `always` rules rare.** Strict rules are brittle. Save them for genuine invariants. - **Name every rule you might want to defeat or prefer.** Bare defeasible rules work fine, but labels make conflict reports readable. - **Put evergreen rules in `.spl` files** and point-in-time facts inside the page they belong to. Rules are stable; facts come and go."},{"h":"Related","l":190,"t":"- [[What is Defeasible Reasoning]] - [[Running Queries]] - [[Proof Trees]] - [[What is SPL]]"}],"tf":{"1":1,"2":1,"a":22,"about":1,"absence":1,"access":1,"across":1,"after":1,"all":1,"alongside":1,"always":1,"an":2,"and":9,"another":2,"anuna":1,"any":2,"anything":1,"anywhere":1,"are":6,"arithmetic":1,"as":2,"ask":1,"assert":1,"asserting":2,"at":2,"back":2,"bare":1,"be":4,"before":1,"belong":1,"better":1,"binds":1,"block":1,"blocked":1,"blocks":2,"body":2,"brittle":1,"broader":1,"bug":1,"but":3,"by":5,"called":1,"can":7,"cannot":1,"care":1,"case":1,"claims":1,"clears":1,"code":1,"codeberg":1,"collisions":1,"combined":1,"come":1,"comments":1,"compete":1,"compose":1,"concluding":1,"conclusion":2,"conclusions":2,"conflict":3,"conjunction":1,"constraints":1,"control":1,"conventional":1,"critical":1,"dashes":1,"declare":1,"default":1,"defeasible":5,"defeat":2,"defeated":2,"defeater":3,"defeaters":1,"defined":1,"docs":1,"does":1,"edit":1,"editors":1,"enough":1,"evergreen":1,"every":4,"everywhere":1,"exact":1,"example":3,"except":2,"exceptions":1,"express":1,"expresses":1,"fact":3,"facts":5,"feed":1,"fenced":1,"few":1,"file":2,"files":2,"fine":1,"first":1,"flags":1,"flavours":1,"flips":1,"folder":1,"for":2,"forbidden":1,"formal":2,"from":1,"genuine":1,"go":2,"goes":1,"grammar":2,"grows":1,"habits":1,"has":1,"have":1,"head":2,"helpful":1,"hold":1,"holds":3,"how":2,"https":1,"identifiers":1,"if":3,"ignored":1,"imagine":1,"implemented":1,"implicit":1,"in":8,"inside":2,"install":1,"installation":1,"into":1,"invariants":1,"is":17,"it":5,"its":1,"just":1,"keep":1,"keyword":1,"label":1,"labels":2,"language":1,"later":2,"learning":1,"legal":1,"like":1,"line":1,"lines":1,"lisp":1,"literal":3,"literals":2,"live":1,"lives":1,"ll":1,"lower":1,"main":1,"make":1,"markdown":1,"meaning":1,"meeting":1,"merges":1,"might":1,"modal":2,"moment":1,"more":1,"motivated":1,"moved":1,"must":1,"name":2,"narrower":1,"never":1,"next":1,"no":1,"non":1,"normal":1,"normally":2,"not":1,"note":1,"obligation":1,"obligatory":1,"of":4,"off":1,"on":2,"once":1,"one":4,"only":2,"open":1,"operator":1,"operators":3,"opposite":1,"optional":1,"or":6,"org":1,"over":2,"page":5,"particular":1,"pay":1,"per":2,"permission":1,"permitted":1,"pick":1,"places":1,"plus":1,"point":3,"policy":1,"prefer":2,"preferences":1,"prefix":2,"prevents":1,"produces":2,"prohibition":1,"project":3,"proof":2,"prose":1,"put":1,"queries":1,"rare":1,"re":1,"readable":1,"readiness":1,"reads":3,"realistic":1,"reason":1,"reasoning":3,"refer":1,"reference":1,"related":1,"remove":1,"reports":2,"requires":1,"research":1,"resolve":1,"rule":13,"rules":12,"run":1,"running":1,"rust":2,"s":1,"same":1,"save":1,"saved":1,"say":1,"says":2,"see":2,"several":1,"shared":1,"ships":1,"short":1,"single":1,"small":1,"something":2,"spell":1,"spindle":3,"spl":7,"stable":1,"standalone":1,"start":1,"starting":1,"state":1,"stated":1,"strict":2,"stronger":1,"syntax":1,"tagged":1,"tangling":1,"temporal":1,"than":1,"that":4,"the":31,"them":4,"theme":1,"theories":1,"theory":1,"these":1,"they":2,"thing":1,"this":5,"three":1,"through":1,"time":1,"to":12,"token":1,"tracking":1,"trees":2,"trivial":1,"true":2,"trumps":1,"trust":1,"two":3,"up":1,"use":3,"useful":2,"vault":6,"via":1,"walks":1,"want":1,"way":1,"weighted":1,"what":2,"when":3,"where":1,"which":2,"wins":1,"with":5,"without":2,"word":1,"work":1,"wrap":1,"write":2,"writing":3,"x":3,"yes":1,"you":11,"your":2,"zetl":4}},{"dl":192,"n":"README","s":"readme","secs":[{"h":"zetl user guide","l":1,"t":"A comprehensive, ~40-page guide to zetl, written as a zetl vault. Everything in this folder is plain Markdown with `[[wikilinks]]`. Start at [Index.md](Index.md)."},{"h":"How to read it","l":7,"t":"Pick whichever you prefer: - **In a web browser** — `make serve` (or `zetl serve --theme quickstart`), then open <http://localhost:3000>. The `quickstart` theme redirects `/` to the Quick Start page; without it you get zetl's default vault landing (stats + page grid) and have to click through to Index. - **As a static site** — `make build` (or `zetl build --theme quickstart --out-dir site`), then upload `site/` to any HTTP host. - **In a terminal** — run `zetl view Index` for the two-pane reader. - **In Obsidian / Logseq / Foam / Dendron** — open this folder as a vault. - **On GitHub / Codeberg** — the wikilinks render as plain text, but every page is readable."},{"h":"About","l":17,"t":"- **What is zetl?** See [getting-started/What is zetl.md](getting-started/What%20is%20zetl.md). - **Canonical zetl source:** <https://codeberg.org/anuna/zetl> - **Scope of this guide:** every feature shipped in the zetl CLI as of v0.5, aimed at writers and knowledge workers."},{"h":"Contributing","l":23,"t":"Found a mistake? Run `zetl check` on the vault and open an issue or PR at the zetl source repository."},{"h":"License","l":27,"t":"Same as zetl: [AGPL-3.0](../LICENSE)."}],"tf":{"0":1,"20is":1,"20zetl":1,"3":1,"3000":1,"40":1,"5":1,"a":7,"about":1,"agpl":1,"aimed":1,"an":1,"and":3,"anuna":1,"any":1,"as":6,"at":3,"browser":1,"but":1,"canonical":1,"cli":1,"click":1,"codeberg":2,"comprehensive":1,"contributing":1,"default":1,"dendron":1,"every":2,"everything":1,"feature":1,"foam":1,"folder":2,"for":1,"found":1,"get":1,"getting":2,"github":1,"grid":1,"guide":3,"have":1,"host":1,"how":1,"http":2,"https":1,"in":5,"index":3,"is":4,"issue":1,"it":2,"knowledge":1,"landing":1,"license":2,"localhost":1,"logseq":1,"markdown":1,"md":4,"mistake":1,"obsidian":1,"of":2,"on":2,"open":3,"or":3,"org":1,"page":4,"pane":1,"pick":1,"plain":2,"pr":1,"prefer":1,"quick":1,"read":1,"readable":1,"reader":1,"redirects":1,"render":1,"repository":1,"run":2,"s":1,"same":1,"scope":1,"see":1,"shipped":1,"site":1,"source":2,"start":2,"started":2,"static":1,"stats":1,"terminal":1,"text":1,"the":7,"theme":1,"then":2,"this":3,"through":1,"to":6,"two":1,"upload":1,"user":1,"v0":1,"vault":4,"web":1,"what":3,"whichever":1,"wikilinks":1,"with":1,"without":1,"workers":1,"writers":1,"written":1,"you":2,"zetl":11}},{"dl":491,"n":"Your First Vault","s":"getting-started/your-first-vault","secs":[{"h":"Your First Vault","l":6,"t":"A vault is just a folder of `.md` files. This page walks you through creating one from scratch — three linked notes, an index, a health check, and a web UI — so you have a feel for vault hygiene before you pour a decade of notes in."},{"h":"Make the folder","l":10,"t":"That's the whole setup. No config files, no database, no `init` command. zetl treats any folder containing Markdown as a vault."},{"h":"Create three linked notes","l":19,"t":"Open your editor and create three files. The content matters less than the wikilinks — we want to see the graph form. **`Projects.md`**: **`2026 Goals.md`**: **`Daily Journal.md`**: Three files. Six wikilinks between them. One of them (`[[2026 Goals#writing]]`) points at a specific heading."},{"h":"Build the index","l":88,"t":"If your terminal shows a table like: …then zetl parsed everything correctly. The `.zetl/` cache now holds the graph, the search index, and (if built with `--features history`) a snapshot."},{"h":"Check vault health","l":107,"t":"With three internally-consistent files, you should see `No issues found` (or an empty diagnostics block in JSON mode). Typical things `check` catches as a vault grows: - **Dead links** — `[[Some Page]]` where no file `Some Page.md` exists. - **Orphan pages** — notes nothing links to and that link to nothing. - **Syntax errors** — malformed wikilinks, broken frontmatter. Run `zetl check --dead-links` or `zetl check --orphans` to isolate one category. See [[Finding Orphans and Dead Links]] for the full workflow."},{"h":"Query the graph","l":121,"t":"Who points at *Projects*? Answer: both `2026 Goals` and `Daily Journal` do. What does *2026 Goals* link out to? Answer: `Daily Journal` and `Projects`. Two hops: zetl finds the shortest path through your link graph — in this three-note case, one hop, but the same command scales to hundred-hop traversals in a large vault."},{"h":"Read it in the browser","l":143,"t":"You get a three-column layout: sidebar with all three pages, rendered Markdown in the middle, backlinks and transclusion cards on the right. Click `[[2026 Goals]]` in `Projects.md` and the page navigates without reload — the graph widget in the corner updates its highlighted node. Edits in the browser save straight back to `~/notes/*.md`. Your text editor sees the update immediately. zetl re-indexes in the background."},{"h":"Wikilink syntax cheat-sheet","l":154,"t":"Everything you just used, plus a few extras you'll want: | Form | Meaning | |------|---------| | `[[Projects]]` | Link to the page `Projects.md`. | | `[[Projects\\|current work]]` | Same link, displayed as \"current work\". | | `[[2026 Goals#Writing]]` | Link to the *Writing* heading in `2026 Goals.md`. | | `[[Projects^abc123]]` | Link to the block with ID `abc123`. | | `![[Daily Journal]]` | Embed (transclude) the target's content. | See [[Wikilinks]] for every variant and [[Linking Pages]] for the day-to-day workflow."},{"h":"What just happened","l":168,"t":"You made a folder, wrote three Markdown files, and pointed zetl at it. That's a vault. Add 397 more files and you have a Zettelkasten. zetl never wrote to any of your notes; everything it needed to remember lives in `~/notes/.zetl/` and can be deleted without losing a word."},{"h":"Next","l":172,"t":"- [[Writing Pages]] — titles, Markdown flavours, frontmatter conventions. - [[Linking Pages]] — aliases, heading links, block references, day-to-day practice. - [[The Link Graph]] — how zetl thinks about your notes. - [[Finding Orphans and Dead Links]] — keeping the vault healthy as it grows."},{"h":"Related","l":179,"t":"- [[Writing Pages]] - [[Linking Pages]] - [[The Link Graph]] - [[Finding Orphans and Dead Links]] - [[Migrating from Obsidian]]"}],"tf":{"2026":1,"397":1,"a":18,"about":1,"add":1,"aliases":1,"all":1,"an":2,"and":15,"answer":2,"any":2,"as":4,"at":3,"back":1,"background":1,"backlinks":1,"be":1,"before":1,"between":1,"block":3,"both":1,"broken":1,"browser":2,"build":1,"built":1,"but":1,"cache":1,"can":1,"cards":1,"case":1,"catches":1,"category":1,"cheat":1,"check":2,"click":1,"column":1,"command":2,"config":1,"consistent":1,"containing":1,"content":2,"conventions":1,"corner":1,"correctly":1,"create":2,"creating":1,"current":1,"database":1,"day":4,"dead":4,"decade":1,"deleted":1,"diagnostics":1,"displayed":1,"do":1,"does":1,"editor":2,"edits":1,"embed":1,"empty":1,"errors":1,"every":1,"everything":3,"exists":1,"extras":1,"feel":1,"few":1,"file":1,"files":7,"finding":3,"finds":1,"first":1,"flavours":1,"folder":4,"for":4,"form":2,"from":2,"frontmatter":2,"full":1,"get":1,"goals":1,"graph":7,"grows":2,"happened":1,"have":2,"heading":3,"health":2,"healthy":1,"highlighted":1,"holds":1,"hop":2,"hops":1,"how":1,"hundred":1,"hygiene":1,"id":1,"if":2,"immediately":1,"in":12,"index":3,"indexes":1,"internally":1,"is":1,"isolate":1,"it":4,"its":1,"json":1,"just":3,"keeping":1,"large":1,"layout":1,"less":1,"like":1,"link":9,"linked":2,"linking":3,"links":6,"lives":1,"ll":1,"losing":1,"made":1,"make":1,"malformed":1,"markdown":4,"matters":1,"meaning":1,"middle":1,"migrating":1,"mode":1,"more":1,"navigates":1,"needed":1,"never":1,"next":1,"no":4,"node":1,"note":1,"notes":6,"nothing":2,"now":1,"obsidian":1,"of":4,"on":1,"one":4,"open":1,"or":2,"orphan":1,"orphans":3,"out":1,"page":3,"pages":7,"parsed":1,"path":1,"plus":1,"pointed":1,"points":2,"pour":1,"practice":1,"projects":1,"query":1,"re":1,"read":1,"references":1,"related":1,"reload":1,"remember":1,"rendered":1,"right":1,"run":1,"s":3,"same":2,"save":1,"scales":1,"scratch":1,"search":1,"see":4,"sees":1,"setup":1,"sheet":1,"shortest":1,"should":1,"shows":1,"sidebar":1,"six":1,"snapshot":1,"so":1,"specific":1,"straight":1,"syntax":2,"table":1,"target":1,"terminal":1,"text":1,"than":1,"that":3,"the":30,"them":2,"then":1,"things":1,"thinks":1,"this":2,"three":9,"through":2,"titles":1,"to":14,"transclude":1,"transclusion":1,"traversals":1,"treats":1,"two":1,"typical":1,"ui":1,"update":1,"updates":1,"used":1,"variant":1,"vault":9,"walks":1,"want":2,"we":1,"web":1,"what":2,"where":1,"who":1,"whole":1,"widget":1,"wikilink":1,"wikilinks":4,"with":4,"without":2,"word":1,"work":1,"workflow":2,"writing":3,"wrote":2,"you":9,"your":7,"zetl":7,"zettelkasten":1}},{"dl":649,"n":"Migrating from Obsidian","s":"getting-started/migrating-from-obsidian","secs":[{"h":"Migrating from Obsidian","l":6,"t":"There is no migration. Point zetl at your Obsidian vault and it works. The two tools read the same Markdown, the same wikilink syntax, and the same YAML frontmatter — your notes are the source of truth for both."},{"h":"The short version","l":10,"t":"Open your vault in Obsidian at the same time. Both read your `.md` files. Neither locks anything. zetl never writes to your notes — only to a disposable cache under `.zetl/`."},{"h":"Syntax compatibility","l":19,"t":"Every wikilink shape Obsidian uses, zetl parses the same way: | Syntax | Meaning | |--------|---------| | `[[Zettelkasten Method]]` | Standard link. | | `[[Zettelkasten Method\\|the method]]` | Aliased display text. | | `[[Zettelkasten Method#History]]` | Link to a specific heading. | | `[[Zettelkasten Method^abc123]]` | Link to a block by ID. | | `![[Book Notes]]` | Embed (transclude) the whole page. | | `![[Book Notes#Ch 3]]` | Embed a section. | See [[Wikilinks]] for the zetl-specific details (resolution rules, ambiguity handling)."},{"h":"Frontmatter","l":34,"t":"zetl parses YAML frontmatter the same way Obsidian does. Existing `tags`, `aliases`, `publish`, and anything else you rely on will surface in `page.frontmatter.*` for templates, and tags flow into [[Tags and Frontmatter]] queries. Custom keys — `status`, `project`, `deadline` — are preserved verbatim and available in templates and hooks. A frontmatter block Obsidian wrote this morning: …just works in zetl with no changes."},{"h":"Hidden folders and `.obsidian/`","l":50,"t":"By default zetl skips dotdirs — `.obsidian/`, `.trash/`, `.git/`, and friends — during vault scans. Your Obsidian config, plugins, and workspace state are invisible to zetl, which is almost always what you want. If you need zetl to walk those folders (for example, to publish `.obsidian/` docs as part of a static site), pass `--include-hidden`: `.git/`, `.zetl/`, and `node_modules/` remain excluded regardless — they're hardcoded in the scanner and not overridable. See [[Organising Your Vault]] for the full layered exclusion model and `.zetlignore`."},{"h":"Obsidian feature → zetl equivalent","l":65,"t":"| Obsidian | zetl | |----------|------| | Backlinks pane | `zetl backlinks \"Page\"` (CLI) or the right-rail backlinks in `zetl serve`. | | Graph view | The Sigma-based graph widget in `zetl serve` / `zetl build`, plus `/_graph` full-screen. See [[The Link Graph]]. | | Quick switcher | `zetl view` opens a page picker; `zetl search` for content. | | Search | `zetl search \"query\"` (full-text), `zetl search --regex`, `zetl similar` for fuzzy page-name matching. | | Tags (`#tag`) | Parsed from YAML `tags: [...]`. Query via `page.frontmatter.tags` in templates. See [[Tags and Frontmatter]]. | | Embeds (`![[...]]`) | Same syntax; rendered inline in `zetl serve` / `zetl build`. See [[Embeds and Transclusion]]. | | Canvas (`.canvas`) | Not supported. Canvas files are skipped. | | Daily Notes plugin | Not supported as a plugin. Build daily files by hand or with a `pre-build` lifecycle hook. | | Templater / Dataview | Not supported. The render-pipeline hooks (see [[Render Pipeline Hooks]]) are the zetl equivalent for transforming content mid-build. | | Publish / Obsidian Sync | `zetl build` writes static HTML; [[Capability URLs]] share live views without a server. |"},{"h":"What doesn't port over","l":80,"t":"The parts of your Obsidian setup that live in `.obsidian/` — plugins, workspace layout, hotkeys, themes, community plugin settings — don't map into zetl, because zetl isn't a GUI editor. Your *notes* port perfectly; your *Obsidian environment* does not. - **Canvas files** (`.canvas`) are skipped. The JSON format isn't parsed. - **Templater / QuickAdd / Dataview / Excalidraw** — these are Obsidian-specific extensions that run inside the Obsidian app. For zetl, the nearest equivalent is [[Lifecycle Hooks]] (shell scripts at pre/post-build) or [[Render Pipeline Hooks]] (AST-level transforms during `zetl build`). - **Daily Notes** as a built-in feature doesn't exist. Either create daily files manually or script them in a hook. - **Plugins** in general. zetl's extension model is render hooks + ecosystem adapters (Pandoc, mdBook, remark). See [[Plugin Ecosystems]]."},{"h":"Running both at once","l":89,"t":"This is the comfortable path while you try zetl. Open your vault in Obsidian as usual; run `zetl serve` from the same directory in another terminal. Both processes read the same `.md` files on disk. - Save a note in Obsidian → zetl's serve watcher picks up the change and re-indexes. - Save a note in `zetl serve`'s browser editor → the file on disk updates; Obsidian's file-watcher reloads. The only coordination point is `.zetl/`, which is zetl's private cache. Obsidian ignores it. Add `.zetl/` to your `.gitignore` if you version-control the vault."},{"h":"Read-only guarantee","l":98,"t":"zetl never modifies your `.md` files during `index`, `check`, `view`, or `build`. `serve` writes only when *you* edit and save through its browser UI. If that still feels too close, run your whole session in a read-only copy:"},{"h":"Related","l":108,"t":"- [[What is zetl]] - [[Quick Start]] - [[Your First Vault]] - [[Wikilinks]] - [[Organising Your Vault]]"}],"tf":{"a":16,"adapters":1,"add":1,"aliased":1,"almost":1,"always":1,"ambiguity":1,"and":17,"another":1,"anything":2,"app":1,"are":7,"as":4,"ast":1,"at":4,"available":1,"backlinks":2,"based":1,"because":1,"block":2,"both":4,"browser":2,"build":3,"built":1,"by":3,"cache":2,"canvas":3,"capability":1,"change":1,"changes":1,"cli":1,"close":1,"comfortable":1,"community":1,"compatibility":1,"config":1,"content":2,"control":1,"coordination":1,"copy":1,"create":1,"custom":1,"daily":4,"dataview":2,"default":1,"details":1,"directory":1,"disk":2,"display":1,"disposable":1,"docs":1,"does":2,"doesn":2,"don":1,"dotdirs":1,"during":3,"ecosystem":1,"ecosystems":1,"edit":1,"editor":2,"either":1,"else":1,"embed":2,"embeds":2,"environment":1,"equivalent":3,"every":1,"example":1,"excalidraw":1,"excluded":1,"exclusion":1,"exist":1,"existing":1,"extension":1,"extensions":1,"feature":2,"feels":1,"file":2,"files":7,"first":1,"flow":1,"folders":2,"for":9,"format":1,"friends":1,"from":3,"frontmatter":6,"full":3,"fuzzy":1,"general":1,"graph":3,"guarantee":1,"gui":1,"hand":1,"handling":1,"hardcoded":1,"heading":1,"hidden":1,"hook":2,"hooks":6,"hotkeys":1,"html":1,"id":1,"if":3,"ignores":1,"in":18,"indexes":1,"inline":1,"inside":1,"into":2,"invisible":1,"is":8,"isn":2,"it":2,"its":1,"json":1,"just":1,"keys":1,"layered":1,"layout":1,"level":1,"lifecycle":2,"link":4,"live":2,"locks":1,"manually":1,"map":1,"markdown":1,"matching":1,"mdbook":1,"meaning":1,"mid":1,"migrating":1,"migration":1,"model":2,"modifies":1,"morning":1,"name":1,"nearest":1,"need":1,"neither":1,"never":2,"no":2,"not":5,"note":2,"notes":5,"obsidian":18,"of":3,"on":3,"once":1,"only":5,"open":2,"opens":1,"or":5,"organising":2,"over":1,"overridable":1,"page":3,"pandoc":1,"pane":1,"parsed":2,"parses":2,"part":1,"parts":1,"pass":1,"path":1,"perfectly":1,"picker":1,"picks":1,"pipeline":3,"plugin":4,"plugins":3,"plus":1,"point":2,"port":2,"post":1,"pre":1,"preserved":1,"private":1,"processes":1,"publish":2,"queries":1,"query":1,"quick":2,"quickadd":1,"rail":1,"re":2,"read":5,"regardless":1,"related":1,"reloads":1,"rely":1,"remain":1,"remark":1,"render":4,"rendered":1,"resolution":1,"right":1,"rules":1,"run":3,"running":1,"s":5,"same":9,"save":3,"scanner":1,"scans":1,"screen":1,"script":1,"scripts":1,"search":1,"section":1,"see":7,"serve":1,"server":1,"session":1,"settings":1,"setup":1,"shape":1,"share":1,"shell":1,"short":1,"sigma":1,"site":1,"skipped":2,"skips":1,"source":1,"specific":3,"standard":1,"start":1,"state":1,"static":2,"still":1,"supported":3,"surface":1,"switcher":1,"sync":1,"syntax":4,"t":5,"tags":4,"templater":2,"templates":3,"terminal":1,"text":2,"that":3,"the":29,"them":1,"themes":1,"there":1,"these":1,"they":1,"this":2,"those":1,"through":1,"time":1,"to":8,"too":1,"tools":1,"transclude":1,"transclusion":1,"transforming":1,"transforms":1,"truth":1,"try":1,"two":1,"ui":1,"under":1,"up":1,"updates":1,"urls":1,"uses":1,"usual":1,"vault":8,"verbatim":1,"version":2,"via":1,"view":1,"views":1,"walk":1,"want":1,"watcher":2,"way":2,"what":3,"when":1,"which":2,"while":1,"whole":2,"widget":1,"wikilink":2,"wikilinks":2,"will":1,"with":2,"without":1,"works":2,"workspace":2,"writes":3,"wrote":1,"yaml":3,"you":6,"your":16,"zetl":21}},{"dl":426,"n":"What is zetl","s":"getting-started/what-is-zetl","secs":[{"h":"What is zetl","l":6,"t":"zetl is a command-line tool that reads a folder of Markdown notes, understands the `[[wikilinks]]` between them, and lets you query, search, and reason over the resulting graph. Your notes stay on your laptop, in the filesystem, in plain `.md` files. zetl just reads them."},{"h":"What it does","l":10,"t":"Point zetl at a folder of notes and it will: - **Parse wikilinks** — every `[[Zettelkasten Method]]`, `[[Book Notes|my reading list]]`, `[[Daily Journal#2026-04-20]]`, and `![[Project Brief]]` embed. - **Build a link graph** — who points at whom, how many hops between two pages, which pages have no inbound links. - **Answer questions** — forward links, backlinks (including multi-hop), shortest path between two pages, orphaned notes, dead links, pages with similar names. - **Search content** — full-text and regex across the vault, with frontmatter and code-block awareness. - **Render the vault** — as a terminal viewer (`zetl view`), a local web server (`zetl serve`), or a static HTML site (`zetl build`). - **Reason over SPL** — optional defeasible-logic layer that reads `spl` code blocks from your notes and draws conclusions with full provenance. See [[What is SPL]]. A concrete example. You have `~/notes/` with 400 Markdown files. You run: zetl tells you which notes cite *Zettelkasten Method* directly, which cite those, and flags every `[[broken link]]` in the vault. No database. No daemon. The cache lives in `~/notes/.zetl/` and is disposable."},{"h":"Who it's for","l":32,"t":"Solo knowledge workers and small teams who: - Already keep notes in plain Markdown and don't want to migrate. - Use `[[wikilinks]]` (Obsidian, Logseq, Foam, Dendron, or hand-written — all work). - Want *command-line* answers about their graph, not just a GUI. - Care about local-first tools that treat your files as source of truth. With `--collab`, small teams can co-edit the same vault over WebSocket with passkey auth and real-time CRDT sync. See [[Running a Team Server]]."},{"h":"What it's not","l":43,"t":"- **Not a server-only SaaS.** zetl runs on your machine. There's no account to sign up for. Team mode is opt-in and self-hosted. - **Not a new Markdown flavour.** zetl reads the Markdown you already write. Wikilink syntax is the same one Obsidian popularised. YAML frontmatter, headings, code fences, task lists — all standard. - **Not a lock-in.** zetl never writes to your notes. The index under `.zetl/` is a cache; delete it and re-run `zetl index`, everything comes back. Your `.md` files are the only durable state. - **Not a replacement for your editor.** Use whatever you already use — Vim, VS Code, Obsidian, a plain text editor. zetl just queries what's there."},{"h":"Next step","l":50,"t":"If you haven't installed it yet, go to [[Installation]]. Otherwise, spend 60 seconds with [[Quick Start]]."},{"h":"Related","l":54,"t":"- [[Installation]] - [[Quick Start]] - [[Your First Vault]] - [[Vaults]] - [[Local-first]]"}],"tf":{"400":1,"60":1,"a":16,"about":2,"account":1,"across":1,"all":2,"already":3,"and":14,"answer":1,"answers":1,"are":1,"as":2,"at":2,"auth":1,"awareness":1,"back":1,"backlinks":1,"between":3,"block":1,"blocks":1,"build":1,"cache":2,"can":1,"care":1,"cite":2,"co":1,"code":4,"comes":1,"command":2,"conclusions":1,"concrete":1,"content":1,"crdt":1,"daemon":1,"database":1,"dead":1,"defeasible":1,"delete":1,"dendron":1,"directly":1,"disposable":1,"does":1,"don":1,"draws":1,"durable":1,"edit":1,"editor":2,"embed":1,"every":2,"everything":1,"example":1,"fences":1,"files":4,"filesystem":1,"first":3,"flags":1,"flavour":1,"foam":1,"folder":2,"for":3,"forward":1,"from":1,"frontmatter":2,"full":2,"go":1,"graph":3,"gui":1,"hand":1,"have":2,"haven":1,"headings":1,"hop":1,"hops":1,"hosted":1,"how":1,"html":1,"if":1,"in":7,"inbound":1,"including":1,"index":1,"installation":2,"installed":1,"is":7,"it":6,"just":3,"keep":1,"knowledge":1,"laptop":1,"layer":1,"lets":1,"line":2,"link":1,"links":3,"lists":1,"lives":1,"local":3,"lock":1,"logic":1,"logseq":1,"machine":1,"many":1,"markdown":5,"method":1,"migrate":1,"mode":1,"multi":1,"names":1,"never":1,"new":1,"next":1,"no":4,"not":6,"notes":8,"obsidian":3,"of":3,"on":2,"one":1,"only":2,"opt":1,"optional":1,"or":2,"orphaned":1,"otherwise":1,"over":3,"pages":4,"parse":1,"passkey":1,"path":1,"plain":3,"point":1,"points":1,"popularised":1,"provenance":1,"queries":1,"query":1,"questions":1,"quick":2,"re":1,"reads":4,"real":1,"reason":2,"regex":1,"related":1,"render":1,"replacement":1,"resulting":1,"run":2,"running":1,"runs":1,"s":4,"saas":1,"same":2,"search":2,"seconds":1,"see":2,"self":1,"server":3,"shortest":1,"sign":1,"similar":1,"site":1,"small":2,"solo":1,"source":1,"spend":1,"spl":2,"standard":1,"start":2,"state":1,"static":1,"stay":1,"step":1,"sync":1,"syntax":1,"t":2,"task":1,"team":2,"teams":2,"tells":1,"terminal":1,"text":2,"that":3,"the":12,"their":1,"them":2,"there":2,"those":1,"time":1,"to":4,"tool":1,"tools":1,"treat":1,"truth":1,"two":2,"under":1,"understands":1,"up":1,"use":3,"vault":5,"vaults":1,"viewer":1,"vim":1,"vs":1,"want":2,"web":1,"websocket":1,"what":5,"whatever":1,"which":3,"who":3,"whom":1,"wikilink":1,"wikilinks":1,"will":1,"with":7,"work":1,"workers":1,"write":1,"writes":1,"written":1,"yaml":1,"yet":1,"you":7,"your":9,"zetl":9,"zettelkasten":1}},{"dl":424,"n":"Quick Start","s":"getting-started/quick-start","secs":[{"h":"Quick Start","l":6,"t":"Sixty seconds from install to first result. This page uses the `demo-vault/` bundled with the zetl repo — a self-referential knowledge base about zetl itself. Run each command; what you see should match what's described."},{"h":"Install zetl","l":10,"t":"**macOS / Linux:** **Windows:** download [zetl-windows-x86_64.zip](https://files.anuna.io/zetl/latest/zetl-windows-x86_64.zip), extract `zetl.exe`, and add it to your `PATH`. The installer puts `zetl` in `~/.local/bin`, generates the man page, and wires up shell completions. Full details, feature flags, and source-build instructions: [[Installation]]."},{"h":"Get the demo vault","l":22,"t":"Clone the zetl repo to get `demo-vault/`:"},{"h":"Build the index","l":32,"t":"You'll see a one-line summary and a JSON block (zetl auto-detects JSON when output is redirected; in a terminal it prints a table). Something like: 27 files, 152 wikilinks, 2 dead links. The index now lives in `demo-vault/.zetl/` and is reused on subsequent runs — zetl only reparses files whose mtime or hash changed."},{"h":"Follow a link","l":52,"t":"Every Markdown file that mentions `[[Scanner]]` gets threaded into the graph. Ask what *Scanner* points at: In a terminal you'll get a table: Each row is one wikilink, with the line number in the source file so you can jump straight to it."},{"h":"Walk backlinks, two hops","l":77,"t":"Which notes point at `Cache` — and which notes point at *those* notes? `--depth 2` means \"include second-hop citations.\" You'll see direct backlinks (hop 1) and transitive ones (hop 2) labelled so you know which is which."},{"h":"Open the web UI","l":87,"t":"Browse to the URL. You get a sidebar with every page, rendered Markdown, a backlink list, a transclusion panel showing excerpts of forward-linked pages, and a mini link-graph widget bottom-right. Edits save straight back to the `.md` file on disk. Ctrl-C stops the server."},{"h":"Reason over the vault","l":96,"t":"> **Requires `--features reason` at install time.** See [[Installation]]. The demo vault has SPL facts and rules sprinkled through its pages — zetl's reasoning engine pulls them out and draws conclusions. You'll see a theory summary and a list of conclusions, each labelled with one of `+D` (definitely provable), `-D` (definitely not), `+d` (defeasibly provable), or `-d` (defeasibly not), along with the source file and line number that grounds each one. For example: Want to know *why* `release-candidate` holds? Ask for the proof tree:"},{"h":"That's the loop","l":127,"t":"1. `zetl index` to scan. 2. `zetl links` / `zetl backlinks` / `zetl search` to query. 3. `zetl serve` or `zetl view` to read. 4. `zetl reason` to draw conclusions (if you've enabled that feature). Next up: set up your own vault in [[Your First Vault]], or skim every command in the [[CLI Overview]]."},{"h":"Related","l":136,"t":"- [[Your First Vault]] - [[CLI Overview]] - [[Installation]] - [[Searching]] - [[Backlinks]]"}],"tf":{"1":2,"152":1,"2":3,"27":1,"3":1,"4":1,"64":2,"a":14,"about":1,"add":1,"along":1,"and":12,"anuna":1,"ask":2,"at":4,"auto":1,"back":1,"backlink":1,"backlinks":3,"base":1,"block":1,"bottom":1,"browse":1,"build":2,"bundled":1,"c":1,"can":1,"changed":1,"citations":1,"cli":2,"clone":1,"command":2,"completions":1,"conclusions":3,"ctrl":1,"dead":1,"defeasibly":2,"definitely":2,"demo":2,"described":1,"details":1,"detects":1,"direct":1,"disk":1,"download":1,"draw":1,"draws":1,"each":4,"edits":1,"enabled":1,"engine":1,"every":3,"example":1,"excerpts":1,"extract":1,"facts":1,"feature":2,"file":4,"files":3,"first":3,"flags":1,"follow":1,"for":2,"forward":1,"from":1,"full":1,"generates":1,"get":4,"gets":1,"graph":2,"grounds":1,"has":1,"hash":1,"holds":1,"hop":3,"hops":1,"https":1,"if":1,"in":7,"include":1,"index":2,"install":3,"installation":3,"installer":1,"instructions":1,"into":1,"io":1,"is":4,"it":3,"its":1,"itself":1,"json":2,"jump":1,"know":2,"knowledge":1,"labelled":2,"latest":1,"like":1,"line":3,"link":2,"linked":1,"links":1,"linux":1,"list":2,"lives":1,"ll":4,"loop":1,"macos":1,"man":1,"markdown":2,"match":1,"means":1,"mentions":1,"mini":1,"mtime":1,"next":1,"not":2,"notes":3,"now":1,"number":2,"of":3,"on":2,"one":4,"ones":1,"only":1,"open":1,"or":4,"out":1,"output":1,"over":1,"overview":2,"own":1,"page":3,"pages":2,"panel":1,"point":2,"points":1,"prints":1,"proof":1,"provable":2,"pulls":1,"puts":1,"query":1,"quick":1,"read":1,"reason":1,"reasoning":1,"redirected":1,"referential":1,"related":1,"rendered":1,"reparses":1,"repo":2,"requires":1,"result":1,"reused":1,"right":1,"row":1,"rules":1,"run":1,"runs":1,"s":3,"save":1,"scan":1,"scanner":1,"searching":1,"second":1,"seconds":1,"see":5,"self":1,"server":1,"set":1,"shell":1,"should":1,"showing":1,"sidebar":1,"sixty":1,"skim":1,"so":2,"something":1,"source":3,"spl":1,"sprinkled":1,"start":1,"stops":1,"straight":2,"subsequent":1,"summary":2,"table":2,"terminal":2,"that":4,"the":21,"them":1,"theory":1,"this":1,"those":1,"threaded":1,"through":1,"time":1,"to":11,"transclusion":1,"transitive":1,"tree":1,"two":1,"ui":1,"up":3,"url":1,"uses":1,"vault":6,"ve":1,"walk":1,"want":1,"web":1,"what":3,"when":1,"which":4,"whose":1,"why":1,"widget":1,"wikilink":1,"wikilinks":1,"windows":3,"wires":1,"with":5,"x86":2,"you":9,"your":4,"zetl":10,"zip":2}},{"dl":290,"n":"Installation","s":"getting-started/installation","secs":[{"h":"Installation","l":6,"t":"zetl ships prebuilt binaries for Linux, macOS, and Windows. The installer script handles platform detection, download, and wiring up the man page and shell completions. If you prefer to build from source, that path is documented below too."},{"h":"Prebuilt binaries (recommended)","l":10,"t":"**macOS and Linux:** The script detects your OS and architecture, downloads the right tarball, installs the binary to `~/.local/bin`, and generates the man page and shell completions. **Windows:** Download `zetl-windows-x86_64.zip` from [files.anuna.io/zetl/latest](https://files.anuna.io/zetl/latest/zetl-windows-x86_64.zip), extract `zetl.exe`, and place it somewhere on your `PATH`."},{"h":"Pinning to a specific version","l":24,"t":""},{"h":"Custom install location","l":30,"t":""},{"h":"What gets installed","l":36,"t":"The prebuilt binaries include the `reason`, `history`, and `mcp` features. See [[#Feature flags]] below if you need a different set."},{"h":"PATH check","l":48,"t":"The installer warns if `~/.local/bin` isn't on your `PATH`. If it isn't: ---"},{"h":"Install from source","l":59,"t":"Requires a Rust toolchain ([rustup.rs](https://rustup.rs/)). `make install` builds with core features only and installs to `$PREFIX` (default `~/.local`). For optional features:"},{"h":"Feature flags","l":84,"t":"| Flag | Unlocks | |------|---------| | *(none)* | Wikilink parsing, graph queries, search, `check`, `view`, `serve`, `build`, collaboration, hooks. | | `reason` | [[Running Queries]], SPL extraction from Markdown, proof trees, what-if, conflict detection, SPL-based access control. | | `history` | [[Time Travel]] with `--at \"3 days ago\"`, [[Watching for Changes]] via `zetl watch`, per-page timeline, `vault.history` in templates. | | `semantic` | Semantic search via embedding model (alongside full-text). | | `mcp` | [[MCP Server]] — expose the graph, search, and reasoning to AI agents. | Collaboration (`--collab`) is always on — no feature flag required. SPL-based access control requires `--features reason`."},{"h":"Shell completions","l":96,"t":"The binary generates completions on demand, in case you want to wire them up manually or use a shell not covered by the installer: Man page:"},{"h":"Verifying the install","l":115,"t":"If that prints, you're done. Head to [[Quick Start]] for your first vault query."},{"h":"Related","l":124,"t":"- [[Quick Start]] - [[What is zetl]] - [[Your First Vault]] - [[CLI Overview]] - [[Configuration]]"}],"tf":{"64":1,"a":4,"access":2,"agents":1,"ai":1,"alongside":1,"always":1,"and":11,"anuna":2,"architecture":1,"based":2,"below":2,"binaries":3,"binary":2,"build":1,"builds":1,"by":1,"case":1,"changes":1,"check":1,"cli":1,"collaboration":2,"completions":4,"configuration":1,"conflict":1,"control":2,"core":1,"covered":1,"custom":1,"default":1,"demand":1,"detection":2,"detects":1,"different":1,"documented":1,"done":1,"download":2,"downloads":1,"embedding":1,"expose":1,"extract":1,"extraction":1,"feature":3,"features":3,"files":2,"first":2,"flag":2,"flags":2,"for":4,"from":4,"full":1,"generates":2,"gets":1,"graph":2,"handles":1,"head":1,"hooks":1,"https":2,"if":6,"in":2,"include":1,"install":3,"installation":1,"installed":1,"installer":3,"installs":2,"io":2,"is":3,"isn":2,"it":2,"latest":2,"linux":2,"location":1,"macos":2,"man":3,"manually":1,"markdown":1,"mcp":1,"model":1,"need":1,"no":1,"none":1,"not":1,"on":4,"only":1,"optional":1,"or":1,"os":1,"overview":1,"page":4,"parsing":1,"path":2,"per":1,"pinning":1,"place":1,"platform":1,"prebuilt":3,"prefer":1,"prints":1,"proof":1,"queries":2,"query":1,"quick":2,"re":1,"reasoning":1,"recommended":1,"related":1,"required":1,"requires":2,"right":1,"rs":2,"running":1,"rust":1,"rustup":2,"script":2,"search":3,"see":1,"semantic":1,"server":1,"set":1,"shell":4,"ships":1,"somewhere":1,"source":2,"specific":1,"spl":3,"start":2,"t":2,"tarball":1,"templates":1,"text":1,"that":2,"the":13,"them":1,"time":1,"timeline":1,"to":7,"too":1,"toolchain":1,"travel":1,"trees":1,"unlocks":1,"up":2,"use":1,"vault":2,"verifying":1,"version":1,"via":2,"want":1,"warns":1,"watching":1,"what":3,"wikilink":1,"windows":3,"wire":1,"wiring":1,"with":2,"x86":1,"you":4,"your":5,"zetl":5,"zip":1}},{"dl":581,"n":"Index","s":"index","secs":[{"h":"zetl — Quick Start","l":6,"t":"**zetl** is a command-line tool that turns a folder of Markdown files with `[[wikilinks]]` into a queryable knowledge graph — with optional defeasible reasoning, vault history, collaborative editing, and an MCP server for AI agents. Your files stay as they are; everything zetl generates lives in a disposable `.zetl/` cache next to them. Source: <https://codeberg.org/anuna/zetl>. This guide is itself a zetl vault — every page you read is a `.md` file in this folder. ---"},{"h":"1. Install","l":14,"t":"**macOS / Linux:** **Windows:** download [zetl-windows-x86_64.zip](https://files.anuna.io/zetl/latest/zetl-windows-x86_64.zip), extract `zetl.exe`, add it to your `PATH`. **Build from source** (requires a [Rust toolchain](https://rustup.rs/)): You get `zetl` on your `$PATH`, a man page, and shell completions. Feature flags, custom install paths: [[Installation]]."},{"h":"2. Get the demo vault","l":34,"t":"The zetl repo ships with `demo-vault/`, a self-referential knowledge base about zetl itself — wikilinks and SPL throughout. From your clone:"},{"h":"3. Build the index","l":43,"t":"zetl auto-detects output format: a table in a terminal, JSON when redirected. You'll see something like: 27 files, 152 wikilinks, 2 dead links. The cache lives in `demo-vault/.zetl/` and is reused on subsequent runs — zetl only reparses files whose mtime or hash changed."},{"h":"4. Follow a link","l":63,"t":"Every Markdown file that mentions `[[Scanner]]` threads into the graph. Ask what *Scanner* points at: Each row is one wikilink, with the line number in the source file."},{"h":"5. Walk backlinks, two hops","l":86,"t":"Which notes point at `Cache` — and which notes point at *those* notes? `--depth 2` means \"include second-hop citations.\" Direct backlinks (hop 1) and transitive ones (hop 2) are labelled so you can tell them apart."},{"h":"6. Open the web UI","l":96,"t":"You get a sidebar with every page, rendered Markdown, a backlink list, a transclusion panel showing excerpts of forward-linked pages, and a mini link-graph widget bottom-right. Edits save straight back to the `.md` file on disk. Ctrl-C stops the server."},{"h":"7. Reason over the vault","l":105,"t":"> **Requires `--features reason` at install time.** See [[Installation]]. The demo vault has SPL facts and rules sprinkled through its pages — zetl's reasoning engine pulls them out and draws conclusions. Tags: `+D` definitely provable, `-D` definitely not, `+d` defeasibly provable, `-d` defeasibly not. Every conclusion carries a line-numbered provenance trail. Ask *why* `release-candidate` holds:"},{"h":"That's the loop","l":135,"t":"1. `zetl index` to scan. 2. `zetl links` / `zetl backlinks` / `zetl search` to query. 3. `zetl serve` or `zetl view` to read. 4. `zetl reason` to draw conclusions (if `--features reason`). ---"},{"h":"Read this guide the same way","l":144,"t":"Everything above works on this vault too. The vault ships with a bundled `quickstart` theme that redirects `/` straight here, so pass `--theme quickstart` (or use the `Makefile`) when you serve or build: Or open the folder in Obsidian, Logseq, Foam, or Dendron — wikilinks work everywhere. ---"},{"h":"Where to go next","l":160,"t":""},{"h":"Start here if you're new","l":162,"t":"- [[What is zetl]] · [[Your First Vault]] · [[Migrating from Obsidian]]"},{"h":"Core concepts","l":165,"t":"- [[Vaults]] · [[Wikilinks]] · [[The Link Graph]] · [[Blocks]] · [[Frontmatter]] · [[Local-first]] · [[What is SPL]] · [[Intellectual Heritage]]"},{"h":"Writing","l":168,"t":"- [[Writing Pages]] · [[Linking Pages]] · [[Embeds and Transclusion]] · [[Headings and Blocks]] · [[Organising Your Vault]] · [[Tags and Frontmatter]]"},{"h":"Finding","l":171,"t":"- [[Searching]] · [[Backlinks]] · [[Similar Pages]] · [[Following Links]] · [[Finding Orphans and Dead Links]]"},{"h":"Reading & rendering","l":174,"t":"- [[Terminal Viewer]] · [[Web Server]] · [[Static Site Export]] · [[Customising the Look]]"},{"h":"Vault history — requires `--features history`","l":177,"t":"- [[Time Travel]] · [[Watching for Changes]] · [[Page History]] · [[Snapshots Under the Hood]]"},{"h":"Reasoning — requires `--features reason`","l":180,"t":"- [[What is Defeasible Reasoning]] · [[Writing SPL]] · [[Running Queries]] · [[Proof Trees]] · [[What-if and Abduction]]"},{"h":"Collaboration","l":183,"t":"- [[Running a Team Server]] · [[Passkeys and Accounts]] · [[Invitations]] · [[Co-editing]] · [[Access Control]] · [[Capability URLs]]"},{"h":"Automation & extensibility","l":186,"t":"- [[Lifecycle Hooks]] · [[Render Pipeline Hooks]] · [[Plugin Ecosystems]] · [[MCP Server]]"},{"h":"Reference","l":189,"t":"- [[CLI Overview]] · [[Configuration]] · [[Frontmatter Fields]] · [[Glossary]]"},{"h":"Help","l":192,"t":"- [[Troubleshooting]] · [[FAQ]]"}],"tf":{"1":3,"152":1,"2":4,"27":1,"3":2,"4":2,"5":1,"6":1,"64":2,"7":1,"a":19,"abduction":1,"about":1,"above":1,"access":1,"accounts":1,"add":1,"agents":1,"ai":1,"an":1,"and":15,"anuna":2,"apart":1,"are":2,"as":1,"ask":2,"at":4,"auto":1,"automation":1,"back":1,"backlink":1,"backlinks":3,"base":1,"blocks":2,"bottom":1,"build":3,"bundled":1,"c":1,"cache":2,"can":1,"capability":1,"carries":1,"changed":1,"changes":1,"citations":1,"cli":1,"clone":1,"co":1,"codeberg":1,"collaboration":1,"collaborative":1,"command":1,"completions":1,"concepts":1,"conclusion":1,"conclusions":2,"configuration":1,"control":1,"core":1,"ctrl":1,"custom":1,"customising":1,"dead":2,"defeasible":2,"defeasibly":2,"definitely":2,"demo":2,"dendron":1,"detects":1,"direct":1,"disk":1,"disposable":1,"download":1,"draw":1,"draws":1,"each":1,"ecosystems":1,"editing":2,"edits":1,"embeds":1,"engine":1,"every":4,"everything":2,"everywhere":1,"excerpts":1,"export":1,"extensibility":1,"extract":1,"facts":1,"faq":1,"feature":1,"fields":1,"file":4,"files":5,"finding":2,"first":2,"flags":1,"foam":1,"folder":3,"follow":1,"following":1,"for":2,"format":1,"forward":1,"from":3,"frontmatter":3,"generates":1,"get":3,"glossary":1,"go":1,"graph":4,"guide":2,"has":1,"hash":1,"headings":1,"help":1,"here":2,"heritage":1,"history":3,"holds":1,"hood":1,"hooks":2,"hop":3,"hops":1,"https":3,"if":3,"in":6,"include":1,"index":1,"install":3,"installation":2,"intellectual":1,"into":2,"invitations":1,"io":1,"is":8,"it":1,"its":1,"itself":2,"json":1,"knowledge":2,"labelled":1,"latest":1,"lifecycle":1,"like":1,"line":3,"link":3,"linked":1,"linking":1,"links":3,"linux":1,"list":1,"lives":2,"ll":1,"local":1,"logseq":1,"look":1,"loop":1,"macos":1,"man":1,"markdown":3,"mcp":2,"means":1,"mentions":1,"migrating":1,"mini":1,"mtime":1,"new":1,"next":2,"not":2,"notes":3,"number":1,"numbered":1,"obsidian":2,"of":2,"on":4,"one":1,"ones":1,"only":1,"open":2,"optional":1,"or":6,"org":1,"organising":1,"orphans":1,"out":1,"output":1,"over":1,"overview":1,"page":4,"pages":5,"panel":1,"pass":1,"passkeys":1,"paths":1,"pipeline":1,"plugin":1,"point":2,"points":1,"proof":1,"provable":2,"provenance":1,"pulls":1,"queries":1,"query":1,"queryable":1,"quick":1,"re":1,"read":3,"reading":1,"reason":1,"reasoning":4,"redirected":1,"redirects":1,"reference":1,"referential":1,"render":1,"rendered":1,"rendering":1,"reparses":1,"repo":1,"requires":4,"reused":1,"right":1,"row":1,"rs":1,"rules":1,"running":2,"runs":1,"rust":1,"rustup":1,"s":2,"same":1,"save":1,"scan":1,"scanner":1,"searching":1,"second":1,"see":2,"self":1,"serve":1,"server":5,"shell":1,"ships":2,"showing":1,"sidebar":1,"similar":1,"site":1,"snapshots":1,"so":2,"something":1,"source":3,"spl":4,"sprinkled":1,"start":2,"static":1,"stay":1,"stops":1,"straight":2,"subsequent":1,"table":1,"tags":2,"team":1,"tell":1,"terminal":2,"that":4,"the":20,"them":3,"theme":1,"they":1,"this":4,"those":1,"threads":1,"through":1,"throughout":1,"time":2,"to":8,"too":1,"tool":1,"toolchain":1,"trail":1,"transclusion":2,"transitive":1,"travel":1,"trees":1,"troubleshooting":1,"turns":1,"two":1,"ui":1,"under":1,"urls":1,"use":1,"vault":10,"vaults":1,"viewer":1,"walk":1,"watching":1,"way":1,"web":2,"what":5,"when":2,"where":1,"which":2,"whose":1,"why":1,"widget":1,"wikilink":1,"wikilinks":4,"windows":3,"with":6,"work":1,"works":1,"writing":3,"x86":2,"you":7,"your":6,"zetl":14,"zip":2}},{"dl":526,"n":"Blocks","s":"concepts/blocks","secs":[{"h":"Blocks","l":6,"t":"zetl breaks every page into **blocks** — headings, paragraphs, code fences, lists, tables, blockquotes, and SPL fragments — and gives each block a stable hash. This turns a page into a small Merkle tree of content, which is what makes `[[Page^block-id]]` links work, what makes the incremental cache fast, and what will later make sync possible."},{"h":"What a block is","l":10,"t":"A block is the smallest unit of page content zetl tracks. Parsing a page yields a sequence of typed blocks: | Type | Example | |------|---------| | `heading` | `## Origins` | | `paragraph` | Regular prose, one logical paragraph per block | | `code` | A fenced `` ``` `` code block (any language) | | `spl` | A fenced ` ```spl ` block (only with `--features reason`) | | `list` | A top-level list; nested items stay inside | | `table` | A pipe or grid table | | `blockquote` | A `> …` quoted section | | `frontmatter` | The YAML header, if present | List what's in a page:"},{"h":"Why content-addressable","l":32,"t":"Every block's content is hashed with [BLAKE3](https://github.com/BLAKE3-team/BLAKE3), and the whole page is a Merkle tree with block hashes as leaves. \"Content-addressable\" means **the hash identifies the content, not a location**. This has three consequences that matter to you."},{"h":"1. Block links survive file moves","l":36,"t":"`[[Some Page^b3a9f1]]` links to the block whose hash starts with `b3a9f1`. Rename `Some Page.md` to `Renamed Page.md` and the hash doesn't change — the block is the same content. zetl can still resolve the link. It keeps pointers to *what you said*, not to *where it lived*. Resolve a hash back to its source: Paste that hash from anywhere (another page, a chat log, a commit message) and get the block back."},{"h":"2. Incremental caching is cheap","l":48,"t":"When you reindex, zetl compares block hashes to the cached ones and skips anything unchanged. Edit one paragraph in a 300-block page, and only that one paragraph's block is re-parsed; the other 299 hashes match the cache, no work. That's how the two-tier index (see [[The Link Graph]]) stays fast on vaults with thousands of pages."},{"h":"3. Deduplication and diffs are natural","l":52,"t":"Two blocks with identical content hash the same — whether they live on the same page, different pages, or different vaults. That's useful for: - Finding where a quote or snippet has been reused. - Diffing a page against a past snapshot (see [[Page History]]) — unchanged blocks line up trivially. - Future sync between vaults: the same block on both sides doesn't need to cross the network."},{"h":"How block links are written","l":60,"t":"When you link to a block you rarely type the hash by hand. The common flow is: 1. Open a page in `zetl serve`; each block has an anchor with its short hash. 2. Copy the `[[Page^hash]]` form from the anchor. 3. Paste into your current note. Obsidian's `^block-id` syntax is supported on read — if you imported a vault from Obsidian, those ids still resolve. See [[Migrating from Obsidian]]."},{"h":"A worked example","l":70,"t":"A page `Luhmann's Workflow.md` with three paragraphs, each hashed independently: Another note can now embed one paragraph, not the whole page: Edit paragraph two; `b3a9f1` becomes something else, and the embed now points at the *new* content at that slot on the page. Block hashes track content; block *anchors* track position within a page. Both are useful, neither is magic."},{"h":"Related","l":91,"t":"- [[Headings and Blocks]] - [[Embeds and Transclusion]] - [[The Link Graph]] - [[Page History]] - [[Wikilinks]]"}],"tf":{"1":2,"2":2,"299":1,"3":2,"300":1,"a":28,"addressable":2,"against":1,"an":1,"anchor":2,"anchors":1,"and":12,"another":2,"any":1,"anything":1,"anywhere":1,"are":3,"as":1,"at":2,"back":2,"becomes":1,"been":1,"between":1,"blake3":3,"block":21,"blockquotes":1,"blocks":6,"both":2,"breaks":1,"by":1,"cache":2,"cached":1,"caching":1,"can":2,"change":1,"chat":1,"cheap":1,"code":2,"com":1,"commit":1,"common":1,"compares":1,"consequences":1,"content":10,"copy":1,"cross":1,"current":1,"deduplication":1,"different":2,"diffing":1,"diffs":1,"doesn":2,"each":3,"edit":2,"else":1,"embed":2,"embeds":1,"every":2,"example":2,"fast":2,"fenced":2,"fences":1,"file":1,"finding":1,"flow":1,"for":1,"form":1,"fragments":1,"from":4,"future":1,"get":1,"github":1,"gives":1,"graph":2,"grid":1,"hand":1,"has":3,"hash":9,"hashed":2,"hashes":4,"header":1,"headings":2,"history":2,"how":2,"https":1,"identical":1,"identifies":1,"ids":1,"if":2,"imported":1,"in":3,"incremental":2,"independently":1,"index":1,"inside":1,"into":3,"is":11,"it":2,"items":1,"its":2,"keeps":1,"language":1,"later":1,"leaves":1,"level":1,"line":1,"link":4,"links":4,"list":2,"lists":1,"live":1,"lived":1,"location":1,"log":1,"logical":1,"magic":1,"make":1,"makes":2,"match":1,"matter":1,"means":1,"merkle":2,"message":1,"migrating":1,"moves":1,"natural":1,"need":1,"neither":1,"nested":1,"network":1,"new":1,"no":1,"not":3,"note":2,"now":2,"obsidian":3,"of":4,"on":5,"one":4,"ones":1,"only":2,"open":1,"or":3,"other":1,"page":17,"pages":2,"paragraph":5,"paragraphs":2,"parsed":1,"parsing":1,"past":1,"paste":2,"per":1,"pipe":1,"pointers":1,"points":1,"position":1,"possible":1,"present":1,"prose":1,"quote":1,"quoted":1,"rarely":1,"re":1,"read":1,"regular":1,"reindex":1,"related":1,"rename":1,"resolve":3,"reused":1,"s":6,"said":1,"same":4,"section":1,"see":3,"sequence":1,"short":1,"sides":1,"skips":1,"slot":1,"small":1,"smallest":1,"snapshot":1,"snippet":1,"something":1,"source":1,"spl":1,"stable":1,"starts":1,"stay":1,"stays":1,"still":2,"supported":1,"survive":1,"sync":2,"syntax":1,"t":2,"table":1,"tables":1,"team":1,"that":6,"the":30,"they":1,"this":2,"those":1,"thousands":1,"three":2,"tier":1,"to":9,"top":1,"track":2,"tracks":1,"transclusion":1,"tree":2,"trivially":1,"turns":1,"two":3,"type":2,"typed":1,"unchanged":2,"unit":1,"up":1,"useful":2,"vault":1,"vaults":3,"what":6,"when":2,"where":2,"whether":1,"which":1,"whole":2,"whose":1,"why":1,"wikilinks":1,"will":1,"with":8,"within":1,"work":2,"worked":1,"written":1,"yaml":1,"yields":1,"you":6,"your":1,"zetl":4}},{"dl":456,"n":"Frontmatter","s":"concepts/frontmatter","secs":[{"h":"Frontmatter","l":6,"t":"**Frontmatter** is the YAML block at the top of a Markdown file, fenced by `---`. zetl reads it, exposes it in templates, and otherwise gets out of the way. It is entirely optional."},{"h":"What it looks like","l":10,"t":"Everything between the two `---` fences is parsed as YAML. Everything after is the page body."},{"h":"What zetl does with it","l":26,"t":"Three things, all passive: 1. **Parses it once per page**, on indexing. 2. **Makes it available in templates** as `page.frontmatter.<key>`. If you're theming `zetl serve` or `zetl build`, you can read anything you've written — `{{ page.frontmatter.tags }}`, `{{ page.frontmatter.status }}`, anything. See [[Customising the Look]]. 3. **Exposes it to hooks** via the vault context, so render-pipeline hooks and lifecycle scripts can make decisions based on metadata. zetl does not require any specific field. No `title`, no `id`, no `uuid`. Your filename is your page name; the graph builds itself from wikilinks."},{"h":"What the community uses it for","l":36,"t":"None of these are built-in — they're conventions that happen to work: | Field | Use | |-------|-----| | `tags` | A list for topical grouping; read by themes and `zetl check`-style tooling | | `date` | Publication or creation date; often used for journal pages | | `status` | `draft`, `published`, `archived` — render differently per status | | `aliases` | Alternate names for the same page; used by some themes for search | | `author` | For multi-author vaults | A minimal page doesn't need any of this. A page with no frontmatter at all is completely fine: See [[Tags and Frontmatter]] for workflow-level patterns."},{"h":"Arbitrary keys are welcome","l":58,"t":"Anything valid YAML is fair game. Nested objects, lists, numbers, booleans — all accessible in templates: `page.frontmatter.project.name` is `\"zetl\"` in your theme. Use this for whatever structure your vault needs — book bibliographies, task metadata, research parameters — without asking permission."},{"h":"Reserved fields","l":74,"t":"A small number of keys have meaning to specific features. The one to know about today: - **`parser:`** — picks a non-default Markdown parser (Pandoc, remark) for pages that need it. Only relevant if you've set up [[Plugin Ecosystems]]. Without a `parser:` field, zetl uses its built-in CommonMark parser. More reserved-key documentation lives at [[Frontmatter Fields]]. If you never use those features, never think about them."},{"h":"A worked example: tag-filtered index","l":82,"t":"Here's why exposing frontmatter to templates matters. Given a vault of project notes each with: A custom `index.html` in `.zetl/themes/default/` can filter the page grid: The vault stays plain Markdown; the presentation reads your metadata. See [[Customising the Look]]."},{"h":"What frontmatter is not","l":104,"t":"- **Not required.** A vault of files with no YAML headers is a valid zetl vault. - **Not validated.** zetl doesn't tell you a field is \"wrong\" — it doesn't know what your fields mean. Invalid YAML (bad indentation) is flagged; semantic choices are yours. - **Not a schema.** If you want a schema, layer one on with [[Lifecycle Hooks]] or [[What is SPL|SPL rules]]."},{"h":"Related","l":110,"t":"- [[Tags and Frontmatter]] - [[Frontmatter Fields]] - [[Writing Pages]] - [[Customising the Look]]"}],"tf":{"1":1,"2":1,"3":1,"a":15,"about":2,"accessible":1,"after":1,"all":3,"alternate":1,"and":5,"any":2,"anything":3,"arbitrary":1,"are":3,"as":2,"asking":1,"at":3,"author":1,"available":1,"bad":1,"based":1,"between":1,"bibliographies":1,"block":1,"body":1,"book":1,"booleans":1,"builds":1,"built":2,"by":3,"can":3,"choices":1,"commonmark":1,"community":1,"completely":1,"context":1,"conventions":1,"creation":1,"custom":1,"customising":3,"date":1,"decisions":1,"default":1,"differently":1,"documentation":1,"does":2,"doesn":3,"each":1,"ecosystems":1,"entirely":1,"everything":2,"example":1,"exposes":2,"exposing":1,"fair":1,"features":2,"fenced":1,"fences":1,"field":4,"fields":4,"file":1,"filename":1,"files":1,"filter":1,"filtered":1,"fine":1,"flagged":1,"for":9,"from":1,"frontmatter":9,"game":1,"gets":1,"given":1,"graph":1,"grid":1,"grouping":1,"happen":1,"have":1,"headers":1,"here":1,"hooks":3,"if":4,"in":7,"indentation":1,"index":1,"indexing":1,"invalid":1,"is":13,"it":11,"its":1,"itself":1,"journal":1,"key":1,"keys":2,"know":2,"layer":1,"level":1,"lifecycle":2,"like":1,"list":1,"lists":1,"lives":1,"look":3,"looks":1,"make":1,"makes":1,"markdown":3,"matters":1,"mean":1,"meaning":1,"metadata":3,"minimal":1,"more":1,"multi":1,"name":1,"names":1,"need":2,"needs":1,"nested":1,"never":2,"no":5,"non":1,"none":1,"not":5,"notes":1,"number":1,"numbers":1,"objects":1,"of":7,"often":1,"on":3,"once":1,"one":2,"only":1,"optional":1,"or":3,"otherwise":1,"out":1,"page":7,"pages":3,"pandoc":1,"parameters":1,"parsed":1,"parser":2,"parses":1,"passive":1,"patterns":1,"per":2,"permission":1,"picks":1,"pipeline":1,"plain":1,"plugin":1,"presentation":1,"project":1,"publication":1,"re":2,"read":2,"reads":2,"related":1,"relevant":1,"remark":1,"render":2,"require":1,"required":1,"research":1,"reserved":2,"rules":1,"s":1,"same":1,"schema":2,"scripts":1,"search":1,"see":3,"semantic":1,"set":1,"small":1,"so":1,"some":1,"specific":2,"spl":2,"status":1,"stays":1,"structure":1,"style":1,"t":3,"tag":1,"tags":2,"task":1,"tell":1,"templates":4,"that":2,"the":16,"them":1,"theme":1,"themes":2,"theming":1,"these":1,"they":1,"things":1,"think":1,"this":2,"those":1,"three":1,"to":5,"today":1,"tooling":1,"top":1,"topical":1,"two":1,"up":1,"use":3,"used":2,"uses":2,"valid":2,"validated":1,"vault":6,"vaults":1,"ve":2,"via":1,"want":1,"way":1,"welcome":1,"what":6,"whatever":1,"why":1,"wikilinks":1,"with":5,"without":2,"work":1,"worked":1,"workflow":1,"writing":1,"written":1,"wrong":1,"yaml":5,"you":7,"your":6,"yours":1,"zetl":6}},{"dl":546,"n":"The Link Graph","s":"concepts/the-link-graph","secs":[{"h":"The Link Graph","l":6,"t":"zetl thinks about your vault as a **directed graph**: pages are nodes, wikilinks are edges. Every command you will run — `backlinks`, `path`, `orphans`, `export`, the graph widget in `zetl serve` — is a query over this graph. Understanding the model makes the rest of zetl obvious."},{"h":"What a node is","l":10,"t":"A **node** is one page: one `.md` file in your vault. The node's identity is its filename (minus `.md`, case-normalised). The node's content is what's between the frontmatter and the end of file. That's it — no IDs, no database keys."},{"h":"What an edge is","l":14,"t":"Every `[[wikilink]]` you write is an **edge** from the current page to the target. Edges are directed: `A.md` writing `[[B]]` creates an edge `A → B`, not `B → A`. Backlinks are the inverse — the set of pages that link *at* you. The graph distinguishes: - **Plain edges** — `[[Page]]`, `[[Page|alias]]` - **Heading edges** — `[[Page#heading]]` - **Block edges** — `[[Page^b3a9f1]]`, which target a specific [[Blocks|block]] - **Embed edges** — `![[Page]]`, same edge, different rendering All four contribute to forward-links and backlinks. An aliased link is still an edge to `Page`, not to `alias`."},{"h":"Dead links and orphans","l":27,"t":"Two graph concepts show up often enough to name: - A **dead link** is an edge whose target has no matching file. The edge exists in the graph; the node on the far end is empty. `zetl check --dead-links` lists them. - An **orphan** is a node with no inbound edges — nothing links *to* it. An orphan may still have outbound links. `zetl check --orphans` lists them. Neither is an error. A dead link can be a stub you intend to fill in. An orphan can be a brand-new note waiting for its first backlink, or an index page whose incoming links are all from the sidebar. See [[Finding Orphans and Dead Links]] for when to care."},{"h":"Backlinks are the graph's secret","l":36,"t":"The real reason wikilink graphs are useful is the backlink view. Your outbound links capture what you were thinking about when you wrote the page. The inbound links capture what the rest of your vault thinks of it — often including connections you forgot you made. Every page in `zetl serve` has a backlinks pane for exactly this reason. See [[Backlinks]]."},{"h":"Traversal","l":40,"t":"Because it's a graph, you can ask graph-shaped questions: - **Multi-hop backlinks.** `zetl backlinks \"Rust\" --depth 2` walks two steps inward. - **Shortest path.** `zetl path \"Note A\" \"Note B\"` finds the chain that connects two ideas, if one exists. - **Following forward links.** `zetl links \"Some Page\"` is the outbound side. See [[Following Links]]. - **Full export.** `zetl export` dumps the whole graph as JSON for external tools."},{"h":"Incremental rebuild","l":49,"t":"The graph is not recomputed from scratch on every command. `zetl index` walks the vault once, and uses a two-tier cache keyed on **mtime + content hash**: 1. If the file's mtime matches the cache, assume unchanged — skip. 2. If the mtime changed, hash the content and compare. If the hash matches (mtime-only touch), skip parsing. 3. Otherwise, reparse the file, update its outbound edges, and invalidate downstream inverses. The result: reindexing a vault after editing three files takes about as long as parsing three files, not the whole tree. The cache lives in `.zetl/` and is safe to delete."},{"h":"Staying out of your way","l":59,"t":"zetl never modifies a page to \"fix\" the graph. A renamed target stays a dead link until you rename its incoming references too. `zetl check` tells you what changed; the edit is yours. See [[Local-first]]."},{"h":"Related","l":63,"t":"- [[Backlinks]] - [[Finding Orphans and Dead Links]] - [[Following Links]] - [[Wikilinks]] - [[Searching]]"}],"tf":{"1":1,"2":1,"3":1,"a":17,"about":3,"after":1,"aliased":1,"all":2,"an":11,"and":9,"are":7,"as":4,"ask":1,"assume":1,"at":1,"backlink":2,"backlinks":7,"be":2,"because":1,"between":1,"block":2,"blocks":1,"brand":1,"cache":3,"can":3,"capture":2,"care":1,"case":1,"chain":1,"changed":2,"command":2,"compare":1,"concepts":1,"connections":1,"connects":1,"content":3,"contribute":1,"creates":1,"current":1,"database":1,"dead":6,"delete":1,"different":1,"directed":2,"distinguishes":1,"downstream":1,"dumps":1,"edge":7,"edges":8,"edit":1,"editing":1,"embed":1,"empty":1,"end":2,"enough":1,"error":1,"every":4,"exactly":1,"exists":2,"export":1,"external":1,"far":1,"file":5,"filename":1,"files":2,"fill":1,"finding":2,"finds":1,"first":2,"fix":1,"following":3,"for":4,"forgot":1,"forward":2,"four":1,"from":3,"frontmatter":1,"full":1,"graph":13,"graphs":1,"has":2,"hash":3,"have":1,"heading":1,"hop":1,"ideas":1,"identity":1,"ids":1,"if":4,"in":6,"inbound":2,"including":1,"incoming":2,"incremental":1,"index":1,"intend":1,"invalidate":1,"inverse":1,"inverses":1,"inward":1,"is":17,"it":4,"its":4,"json":1,"keyed":1,"keys":1,"link":6,"links":12,"lists":2,"lives":1,"local":1,"long":1,"made":1,"makes":1,"matches":2,"matching":1,"may":1,"minus":1,"model":1,"modifies":1,"mtime":4,"multi":1,"name":1,"neither":1,"never":1,"new":1,"no":4,"node":6,"nodes":1,"normalised":1,"not":4,"note":1,"nothing":1,"obvious":1,"of":6,"often":2,"on":3,"once":1,"one":3,"only":1,"or":1,"orphan":3,"orphans":3,"otherwise":1,"out":1,"outbound":4,"over":1,"page":6,"pages":2,"pane":1,"parsing":2,"path":1,"plain":1,"query":1,"questions":1,"real":1,"reason":2,"rebuild":1,"recomputed":1,"references":1,"reindexing":1,"related":1,"rename":1,"renamed":1,"rendering":1,"reparse":1,"rest":2,"result":1,"run":1,"s":7,"safe":1,"same":1,"scratch":1,"searching":1,"secret":1,"see":4,"set":1,"shaped":1,"shortest":1,"show":1,"side":1,"sidebar":1,"skip":2,"specific":1,"staying":1,"stays":1,"steps":1,"still":2,"stub":1,"takes":1,"target":4,"tells":1,"that":3,"the":40,"them":2,"thinking":1,"thinks":2,"this":2,"three":2,"tier":1,"to":10,"too":1,"tools":1,"touch":1,"traversal":1,"tree":1,"two":4,"unchanged":1,"understanding":1,"until":1,"up":1,"update":1,"useful":1,"uses":1,"vault":5,"view":1,"waiting":1,"walks":2,"way":1,"were":1,"what":6,"when":2,"which":1,"whole":2,"whose":2,"widget":1,"wikilink":1,"wikilinks":2,"will":1,"with":1,"write":1,"writing":1,"wrote":1,"you":11,"your":5,"yours":1,"zetl":3}},{"dl":365,"n":"Vaults","s":"concepts/vaults","secs":[{"h":"Vaults","l":6,"t":"A **vault** is any directory that contains Markdown files. That is the whole definition. zetl does not own your notes, does not require a config file, and does not need to import anything — point it at a folder and it starts working."},{"h":"What's in a vault","l":10,"t":"A vault is a tree of `.md` files. Folders are just folders; there is no hidden database beside your notes. The only thing zetl adds is a `.zetl/` directory at the vault root, used for caching: Everything in `.zetl/` is disposable. Delete it and the next `zetl index` rebuilds it from your files. This matters: your vault is your Markdown, not zetl's cache. See [[Local-first]]."},{"h":"How zetl walks a vault","l":30,"t":"When you run `zetl index`, zetl walks the tree using a layered ignore stack. From lowest to highest precedence: | Layer | Rule | |-------|------| | 1 | Hardcoded: `.git/`, `.zetl/`, `node_modules/` are never scanned | | 2 | Dotdirs like `.obsidian/`, `.vscode/`, `.claude/` are skipped by default (`--include-hidden` disables) | | 3 | `.gitignore` patterns, if present | | 4 | `.zetlignore` at the vault root (gitignore syntax; `!pattern` re-includes) | | 5 | `--exclude PATTERN` CLI flag (repeatable, highest priority) | This means a zetl vault coexists cleanly with a git repo, a node project, an Obsidian workspace, or a Logseq graph. You do not need to rearrange files."},{"h":"Pointing zetl at a vault","l":44,"t":"Two ways, and they are equivalent: Run zetl with no `-d` and no `ZETL_DIR`, and it uses the current directory."},{"h":"You can have many vaults","l":60,"t":"A vault is just a folder, so you can keep as many as you like — one for research, one for journaling, one for client work — each with its own `.zetl/` cache and its own graph. They do not share any state. Switching is just `cd` or `-d`. Some people keep one enormous vault and lean on folders plus tags; others keep several small ones. Neither is wrong. See [[Organising Your Vault]]."},{"h":"What a vault is *not*","l":66,"t":"- **Not a database.** Your notes are flat files. Open them in any editor; edit them with any tool. - **Not a format.** zetl reads the Markdown you already have. No special syntax is required. Wikilinks and frontmatter are optional. - **Not locked in.** If you stop using zetl tomorrow, you still have a directory of Markdown files. Everything zetl added is in `.zetl/`."},{"h":"Related","l":72,"t":"- [[Your First Vault]] - [[Local-first]] - [[Organising Your Vault]] - [[Configuration]]"}],"tf":{"1":1,"2":1,"3":1,"4":1,"5":1,"a":20,"added":1,"adds":1,"already":1,"an":1,"and":9,"any":4,"anything":1,"are":6,"as":2,"at":4,"beside":1,"by":1,"cache":2,"caching":1,"can":2,"cleanly":1,"cli":1,"client":1,"coexists":1,"config":1,"configuration":1,"contains":1,"current":1,"database":2,"default":1,"definition":1,"delete":1,"directory":4,"disables":1,"disposable":1,"do":2,"does":3,"dotdirs":1,"each":1,"edit":1,"editor":1,"enormous":1,"equivalent":1,"everything":2,"file":1,"files":6,"first":3,"flag":1,"flat":1,"folder":2,"folders":3,"for":4,"format":1,"from":2,"frontmatter":1,"git":1,"gitignore":1,"graph":2,"hardcoded":1,"have":3,"hidden":1,"highest":2,"how":1,"if":2,"ignore":1,"import":1,"in":5,"includes":1,"is":13,"it":5,"its":2,"journaling":1,"just":3,"keep":3,"layer":1,"layered":1,"lean":1,"like":2,"local":2,"locked":1,"logseq":1,"lowest":1,"many":2,"markdown":4,"matters":1,"means":1,"need":2,"neither":1,"never":1,"next":1,"no":4,"node":1,"not":10,"notes":3,"obsidian":1,"of":2,"on":1,"one":4,"ones":1,"only":1,"open":1,"optional":1,"or":2,"organising":2,"others":1,"own":3,"patterns":1,"people":1,"plus":1,"point":1,"pointing":1,"precedence":1,"present":1,"priority":1,"project":1,"re":1,"reads":1,"rearrange":1,"rebuilds":1,"related":1,"repeatable":1,"repo":1,"require":1,"required":1,"research":1,"root":2,"rule":1,"run":2,"s":2,"scanned":1,"see":2,"several":1,"share":1,"skipped":1,"small":1,"so":1,"some":1,"special":1,"stack":1,"starts":1,"state":1,"still":1,"stop":1,"switching":1,"syntax":2,"tags":1,"that":2,"the":8,"them":2,"there":1,"they":2,"thing":1,"this":2,"to":3,"tomorrow":1,"tool":1,"tree":2,"two":1,"used":1,"uses":1,"using":2,"vault":15,"vaults":2,"walks":2,"ways":1,"what":2,"when":1,"whole":1,"wikilinks":1,"with":4,"work":1,"working":1,"workspace":1,"wrong":1,"you":8,"your":9,"zetl":11}},{"dl":1193,"n":"Intellectual Heritage","s":"concepts/intellectual-heritage","secs":[{"h":"Intellectual Heritage","l":6,"t":"zetl sits at the end of a long line of ideas about how humans extend their memory and organise thought. None of the concepts behind it — linked notes, associative trails, transclusion, graph queries — were invented last decade. This page traces the lineage so you can see what zetl inherits, and what it deliberately leaves out. ---"},{"h":"The Zettelkasten (16th century – present)","l":12,"t":"The name \"zetl\" comes from *Zettelkasten* — German for \"slip-box\" or \"card file\". It is a note-taking method in which ideas are written on individual slips of paper, each given a unique identifier, and then linked to each other by subject headings or explicit cross-references. The practice is older than it sounds. Conrad Gessner (1516–1565) invented an early version in which individual notes could be rearranged at will — an explicit break from the bound commonplace book tradition. Thomas Harrison's \"Ark of Studies\" (*Arca studiorum*, c. 1640s) described a small cabinet where notes were attached to metal hooks labelled by subject heading. Carl Linnaeus used standardised paper slips to record research in 1767. The method's most famous practitioner was the German sociologist Niklas Luhmann (1927–1998). Starting in 1952–1953, Luhmann built a Zettelkasten of roughly 90,000 index cards. He assigned each card a unique hierarchical number — branching off parent cards using decimal notation — so that new ideas could be inserted anywhere in the structure without renumbering everything. He credited the system with enabling around 50 books and 550 articles. Luhmann described the Zettelkasten not as a filing cabinet but as a \"communication partner\": the process of writing new cards and navigating old ones would surface unexpected connections that he had not consciously planned. The Zettelkasten's key insight is that a note's value comes less from its content than from its *connections*. A note isolated in a folder is static; the same note linked to twenty others becomes part of an emergent structure that can surprise its author. zetl inherits the vocabulary directly: a *vault* is a Zettelkasten, a *wikilink* is a slip-to-slip reference, `zetl backlinks` gives you Luhmann's \"which cards point here?\" lookup. ---"},{"h":"The Memex (1945)","l":26,"t":"In July 1945, Vannevar Bush — then head of the US Office of Scientific Research and Development — published an essay called \"As We May Think\" in *The Atlantic*. In it he described a hypothetical device he called the **Memex**: > \"A memex is a device in which an individual stores all his books, records, and communications, and which is mechanised so that it may be consulted with exceeding speed and flexibility. It is an enlarged intimate supplement to his memory.\" Bush's Memex would have been a large desk with microfilm storage. The user could call up any document with a few keystrokes, annotate it, and — crucially — create *associative trails*: named chains of linked frames that could be stored, shared with colleagues, and re-entered later. The trail was Bush's answer to what he saw as the fatal flaw of conventional indexing: \"the human mind operates by association. With one item in its grasp, it snaps instantly to the next that is suggested by the association of thoughts.\" The Memex was never built — the microfilm technology of the day could not support it — but the conceptual architecture shaped nearly every hypertext system that followed. The associative trail is, in essence, a named link graph. Bush's complaint about rigid hierarchical filing (\"artificiality\") is the same complaint that motivates most modern personal knowledge management tools, including zetl. ---"},{"h":"Project Xanadu and Hypertext (1960s)","l":38,"t":"Ted Nelson coined the word **hypertext** in 1963 and spent the next several decades trying to build a working implementation through **Project Xanadu**. Nelson's insight went further than Bush's in one important way: he wanted links to be *bidirectional and permanent*. On the web as it exists today, a link is a one-way pointer; the target page has no way of knowing who links to it, and links break freely when pages move. Nelson envisioned a system where every link was a first-class object, every document had a permanent address, and any section of any document could be embedded — *transcluded* — inside any other, with an unbreakable reference back to the original. He called documents assembled from pieces of other documents **compound documents**, assembled via **zippered lists**. Xanadu's other key concept was **transclusion**: rather than copying text into a new document, you include a live reference to the original. When the source changes, so does every transclusion. Nelson saw this as solving plagiarism, enabling micropayment for content, and making the web's link-rot problem structurally impossible. Project Xanadu was notoriously difficult to ship — Autodesk funded a version in the 1980s that was never completed — but nearly every idea in it has since been partially implemented somewhere. zetl's `![[embed]]` syntax is transclusion in exactly Nelson's sense: the embedded content renders from the source file; it is not a copy. ---"},{"h":"WikiWikiWeb (1995)","l":50,"t":"Ward Cunningham launched **WikiWikiWeb** in 1995 — the first publicly editable website. The name came from the Hawaiian word *wiki*, meaning \"quick\". Cunningham described it as \"the simplest online database that could possibly work\". The wiki's key design decisions were: - Any page could link to any other page, and if the target didn't exist yet, clicking the link created it. - Any user could edit any page, with no access control by default. - The markup was intentionally plain — a lightweight syntax that kept source text readable. Wikipedia launched in 2001 on wiki software, eventually becoming the largest reference work ever assembled (7 million English-language articles as of 2026). Enterprise wikis — Confluence, MediaWiki, Notion — replicated the model in controlled environments. The wiki model proved that linked, collaboratively editable text could scale to enormous size. It also revealed the model's limits: wikis have little inherent structure; they tend toward disorganisation without active curation; and they are built for *shared* knowledge rather than *personal* thinking. The personal knowledge management tools that followed — Roam, Obsidian, Logseq, zetl — took the wikilink as the fundamental primitive while reorienting the tool toward individual use, offline ownership, and structured query. ---"},{"h":"Where zetl fits","l":65,"t":"zetl is not trying to be any of the above. It takes specific, bounded ideas from each: | Ancestor | What zetl takes | |----------|----------------| | Zettelkasten | Local files as primary record; links as first-class objects; backlink traversal | | Memex | Associative trails surfaced by graph queries (`zetl links`, `zetl backlinks`) | | Xanadu | Transclusion (`![[embed]]`); bidirectional links; vault-first permanence | | Wiki | Wikilink syntax; human-readable Markdown; web UI for browsing | What zetl explicitly does *not* do: it does not own your data (files stay as Markdown), it does not require a server (the index is a local cache), and it does not try to replace your editor. The intellectual heritage here is long — but the design constraint is minimal. ---"},{"h":"Further reading","l":80,"t":"- Vannevar Bush, \"As We May Think\", *The Atlantic*, July 1945 - Ted Nelson, *Literary Machines* (1981) — original Xanadu design - Niklas Luhmann, \"Kommunikation mit Zettelkästen\" (1981) — available in English translation - Ward Cunningham and Bo Leuf, *The Wiki Way* (2001) - Sönke Ahrens, *How to Take Smart Notes* (2017) — accessible modern treatment of Luhmann's method"},{"h":"Related","l":88,"t":"- [[What is zetl]] - [[Wikilinks]] - [[The Link Graph]] - [[Embeds and Transclusion]] - [[Vaults]]"}],"tf":{"000":1,"1516":1,"1565":1,"1640s":1,"16th":1,"1767":1,"1927":1,"1945":3,"1952":1,"1953":1,"1960s":1,"1963":1,"1980s":1,"1981":2,"1995":2,"1998":1,"2001":2,"2017":1,"2026":1,"50":1,"550":1,"7":1,"90":1,"a":34,"about":2,"above":1,"access":1,"accessible":1,"active":1,"address":1,"ahrens":1,"all":1,"also":1,"an":7,"ancestor":1,"and":23,"annotate":1,"answer":1,"any":9,"anywhere":1,"arca":1,"architecture":1,"are":2,"ark":1,"around":1,"articles":2,"artificiality":1,"as":13,"assembled":3,"assigned":1,"association":2,"associative":4,"at":2,"atlantic":2,"attached":1,"author":1,"autodesk":1,"available":1,"back":1,"backlink":1,"be":7,"becomes":1,"becoming":1,"been":2,"behind":1,"bidirectional":2,"bo":1,"book":1,"books":2,"bound":1,"bounded":1,"box":1,"branching":1,"break":2,"browsing":1,"build":1,"built":3,"bush":6,"but":4,"by":6,"c":1,"cabinet":2,"cache":1,"call":1,"called":3,"came":1,"can":2,"card":2,"cards":4,"carl":1,"century":1,"chains":1,"changes":1,"class":2,"clicking":1,"coined":1,"collaboratively":1,"colleagues":1,"comes":2,"commonplace":1,"communication":1,"communications":1,"complaint":2,"completed":1,"compound":1,"concept":1,"concepts":1,"conceptual":1,"confluence":1,"connections":2,"conrad":1,"consciously":1,"constraint":1,"consulted":1,"content":3,"control":1,"controlled":1,"conventional":1,"copy":1,"copying":1,"could":10,"create":1,"created":1,"credited":1,"cross":1,"crucially":1,"cunningham":3,"curation":1,"data":1,"database":1,"day":1,"decade":1,"decades":1,"decimal":1,"decisions":1,"default":1,"deliberately":1,"described":4,"design":3,"desk":1,"development":1,"device":2,"didn":1,"difficult":1,"directly":1,"disorganisation":1,"do":1,"document":4,"documents":3,"does":5,"each":4,"early":1,"edit":1,"editable":2,"editor":1,"embedded":2,"embeds":1,"emergent":1,"enabling":2,"end":1,"english":2,"enlarged":1,"enormous":1,"entered":1,"enterprise":1,"environments":1,"envisioned":1,"essay":1,"essence":1,"eventually":1,"ever":1,"every":5,"everything":1,"exactly":1,"exceeding":1,"exist":1,"exists":1,"explicit":2,"explicitly":1,"extend":1,"famous":1,"fatal":1,"few":1,"file":2,"files":2,"filing":2,"first":4,"fits":1,"flaw":1,"flexibility":1,"folder":1,"followed":2,"for":4,"frames":1,"freely":1,"from":8,"fundamental":1,"funded":1,"further":2,"german":2,"gessner":1,"given":1,"gives":1,"graph":4,"grasp":1,"had":2,"harrison":1,"has":2,"have":2,"hawaiian":1,"he":8,"head":1,"heading":1,"headings":1,"here":2,"heritage":2,"hierarchical":2,"his":2,"hooks":1,"how":2,"human":2,"humans":1,"hypertext":3,"hypothetical":1,"idea":1,"ideas":4,"identifier":1,"if":1,"implementation":1,"implemented":1,"important":1,"impossible":1,"in":21,"include":1,"including":1,"index":2,"indexing":1,"individual":4,"inherent":1,"inherits":2,"inserted":1,"inside":1,"insight":2,"instantly":1,"intellectual":2,"intentionally":1,"intimate":1,"into":1,"invented":2,"is":20,"isolated":1,"it":21,"item":1,"its":4,"july":2,"kept":1,"key":3,"keystrokes":1,"knowing":1,"knowledge":3,"kommunikation":1,"labelled":1,"language":1,"large":1,"largest":1,"last":1,"later":1,"launched":2,"leaves":1,"less":1,"leuf":1,"lightweight":1,"limits":1,"line":1,"lineage":1,"link":7,"linked":5,"links":5,"linnaeus":1,"lists":1,"literary":1,"little":1,"live":1,"local":2,"logseq":1,"long":2,"lookup":1,"luhmann":6,"machines":1,"making":1,"management":2,"markdown":2,"markup":1,"may":3,"meaning":1,"mechanised":1,"mediawiki":1,"memex":6,"memory":2,"metal":1,"method":3,"microfilm":2,"micropayment":1,"million":1,"mind":1,"minimal":1,"mit":1,"model":3,"modern":2,"most":2,"motivates":1,"move":1,"name":2,"named":2,"navigating":1,"nearly":2,"nelson":6,"never":2,"new":3,"next":2,"niklas":2,"no":2,"none":1,"not":9,"notation":1,"note":4,"notes":4,"notion":1,"notoriously":1,"number":1,"object":1,"objects":1,"obsidian":1,"of":20,"off":1,"office":1,"offline":1,"old":1,"older":1,"on":3,"one":3,"ones":1,"online":1,"operates":1,"or":2,"organise":1,"original":3,"other":5,"others":1,"out":1,"own":1,"ownership":1,"page":5,"pages":1,"paper":2,"parent":1,"part":1,"partially":1,"partner":1,"permanence":1,"permanent":2,"personal":3,"pieces":1,"plagiarism":1,"plain":1,"planned":1,"point":1,"pointer":1,"possibly":1,"practice":1,"practitioner":1,"present":1,"primary":1,"primitive":1,"problem":1,"process":1,"project":3,"proved":1,"publicly":1,"published":1,"queries":2,"query":1,"quick":1,"rather":2,"re":1,"readable":2,"reading":1,"rearranged":1,"record":2,"records":1,"reference":4,"references":1,"related":1,"renders":1,"renumbering":1,"reorienting":1,"replace":1,"replicated":1,"require":1,"research":2,"revealed":1,"rigid":1,"roam":1,"rot":1,"roughly":1,"s":17,"same":2,"saw":2,"scale":1,"scientific":1,"section":1,"see":1,"sense":1,"server":1,"several":1,"shaped":1,"shared":2,"ship":1,"simplest":1,"since":1,"sits":1,"size":1,"slip":3,"slips":2,"small":1,"smart":1,"snaps":1,"so":4,"sociologist":1,"software":1,"solving":1,"somewhere":1,"sounds":1,"source":3,"specific":1,"speed":1,"spent":1,"standardised":1,"starting":1,"static":1,"stay":1,"storage":1,"stored":1,"stores":1,"structurally":1,"structure":3,"structured":1,"studies":1,"studiorum":1,"subject":2,"suggested":1,"supplement":1,"support":1,"surface":1,"surfaced":1,"surprise":1,"syntax":3,"system":3,"sönke":1,"t":1,"take":1,"takes":2,"taking":1,"target":2,"technology":1,"ted":2,"tend":1,"text":3,"than":5,"that":14,"the":66,"their":1,"then":2,"they":2,"think":2,"thinking":1,"this":2,"thomas":1,"thought":1,"thoughts":1,"through":1,"to":19,"today":1,"took":1,"tool":1,"tools":2,"toward":2,"traces":1,"tradition":1,"trail":2,"trails":3,"transcluded":1,"transclusion":6,"translation":1,"traversal":1,"treatment":1,"try":1,"trying":2,"twenty":1,"ui":1,"unbreakable":1,"unexpected":1,"unique":2,"up":1,"us":1,"use":1,"used":1,"user":2,"using":1,"value":1,"vannevar":2,"vault":2,"vaults":1,"version":2,"via":1,"vocabulary":1,"wanted":1,"ward":2,"was":8,"way":4,"we":2,"web":3,"website":1,"went":1,"were":3,"what":6,"when":2,"where":3,"which":5,"while":1,"who":1,"wiki":6,"wikilink":3,"wikilinks":1,"wikipedia":1,"wikis":2,"wikiwikiweb":2,"will":1,"with":8,"without":2,"word":2,"work":2,"working":1,"would":2,"writing":1,"written":1,"xanadu":6,"yet":1,"you":3,"your":2,"zetl":12,"zettelkasten":7,"zettelkästen":1,"zippered":1}},{"dl":564,"n":"Local-first","s":"concepts/local-first","secs":[{"h":"Local-first","l":6,"t":"zetl is local-first. That phrase is doing real work — not \"runs locally\" and not \"offline-capable\", but a specific design stance about whose computer owns your notes. This page is the philosophy; every other design choice in zetl follows from it."},{"h":"The principles","l":10,"t":"**Your files are the source of truth.** Not a database, not a cloud tenant, not a proprietary binary format. A zetl vault is a directory of plain `.md` files. Whatever any zetl command says about your vault, the Markdown on disk is the authority. If zetl and the files disagree, the files win. **zetl never modifies your Markdown.** Indexing, reasoning, rendering, validation — none of these rewrite your notes. Wikilinks stay wikilinks; frontmatter stays whatever YAML you wrote. The only bytes zetl writes are under `.zetl/` (its cache) and, optionally, an output directory from `zetl build`. **Everything in `.zetl/` is disposable.** Link index, block hashes, snapshot history, reasoning theory — all of it is cache, derived from your files. Delete `.zetl/` and `zetl index` rebuilds it. Commit your vault without it; share vaults across machines by syncing the `.md` files alone. **Works offline.** zetl is a single binary that reads from and writes to your local filesystem. No network is required for any core operation — indexing, searching, viewing, building, reasoning. A machine in airplane mode has the full feature set. **Data outlives the tool.** If zetl disappears tomorrow, or if you decide it's not for you, your vault is still a folder of Markdown files. Open them in any text editor, any other wikilink tool, any static site generator. You did not sign up for a migration problem."},{"h":"Why this matters for knowledge work","l":22,"t":"A second brain is only useful if you trust it to still be there in ten years. Most knowledge tools violate at least one of the above: they own the format, they own the storage, they own the sync layer, or they break when offline. The cost compounds — every note you write increases your exposure to someone else's product decisions. Local-first flips the dependency. You own the files. Tools like zetl *read* them and give you views — a graph, a search index, a rendered site, a reasoning layer — without taking ownership. When a better tool comes along, your vault is already portable to it."},{"h":"What local-first does *not* mean","l":28,"t":"- **Not single-machine.** You can sync a vault across devices with git, Syncthing, iCloud, Dropbox, rsync, or whatever you already use. zetl does not care how the files got there. - **Not single-user.** zetl has an optional multi-user mode (`zetl serve --collab`) with real-time CRDT editing. But even there, the authoritative state is the Markdown on disk — the server commits changes back to files on save. See [[Running a Team Server]]. - **Not anti-cloud.** Publish a vault to a static host with `zetl build`. Hand out [[Capability URLs]] for private reads. The cloud is a place you choose to put copies, not a place your notes live by default."},{"h":"How zetl stays out of your way","l":34,"t":"| What could go wrong | What zetl does instead | |---------------------|------------------------| | Fix your \"broken\" wikilinks | Reports them; you choose the fix | | Auto-create missing pages | Leaves dead links alone | | Normalise your frontmatter | Reads whatever YAML you wrote | | Rewrite your files on build | Reads them; writes output separately | | Require a config file | Works with zero configuration | This restraint is the feature."},{"h":"Related","l":46,"t":"- [[Vaults]] - [[What is zetl]] - [[Local-first|Data ownership]] - [[Static Site Export]]"}],"tf":{"a":23,"about":2,"above":1,"across":2,"airplane":1,"all":1,"alone":2,"along":1,"already":2,"an":2,"and":6,"anti":1,"any":5,"are":2,"at":1,"authoritative":1,"authority":1,"auto":1,"back":1,"be":1,"better":1,"binary":2,"block":1,"brain":1,"break":1,"broken":1,"build":1,"building":1,"but":2,"by":2,"bytes":1,"cache":2,"can":1,"capability":1,"capable":1,"care":1,"changes":1,"choice":1,"choose":2,"cloud":3,"comes":1,"command":1,"commit":1,"commits":1,"compounds":1,"computer":1,"config":1,"configuration":1,"copies":1,"core":1,"cost":1,"could":1,"crdt":1,"create":1,"data":2,"database":1,"dead":1,"decide":1,"decisions":1,"default":1,"delete":1,"dependency":1,"derived":1,"design":2,"devices":1,"did":1,"directory":2,"disagree":1,"disappears":1,"disk":2,"disposable":1,"does":3,"doing":1,"dropbox":1,"editing":1,"editor":1,"else":1,"even":1,"every":2,"everything":1,"export":1,"exposure":1,"feature":2,"file":1,"files":11,"filesystem":1,"first":5,"fix":2,"flips":1,"folder":1,"follows":1,"for":5,"format":2,"from":4,"frontmatter":2,"full":1,"generator":1,"git":1,"give":1,"go":1,"got":1,"graph":1,"hand":1,"has":2,"hashes":1,"history":1,"host":1,"how":2,"icloud":1,"if":4,"in":5,"increases":1,"index":2,"indexing":2,"instead":1,"is":16,"it":7,"its":1,"knowledge":2,"layer":2,"least":1,"leaves":1,"like":1,"link":1,"links":1,"live":1,"local":6,"locally":1,"machine":2,"machines":1,"markdown":4,"matters":1,"mean":1,"migration":1,"missing":1,"mode":2,"modifies":1,"most":1,"multi":1,"network":1,"never":1,"no":1,"none":1,"normalise":1,"not":13,"note":1,"notes":3,"of":7,"offline":3,"on":4,"one":1,"only":2,"open":1,"operation":1,"optional":1,"optionally":1,"or":3,"other":2,"out":2,"outlives":1,"output":2,"own":4,"ownership":2,"owns":1,"page":1,"pages":1,"philosophy":1,"phrase":1,"place":2,"plain":1,"portable":1,"principles":1,"private":1,"problem":1,"product":1,"proprietary":1,"publish":1,"put":1,"read":1,"reads":4,"real":2,"reasoning":4,"rebuilds":1,"related":1,"rendered":1,"rendering":1,"reports":1,"require":1,"required":1,"restraint":1,"rewrite":2,"rsync":1,"running":1,"runs":1,"s":2,"save":1,"says":1,"search":1,"searching":1,"second":1,"see":1,"separately":1,"server":2,"set":1,"share":1,"sign":1,"single":3,"site":3,"snapshot":1,"someone":1,"source":1,"specific":1,"stance":1,"state":1,"static":3,"stay":1,"stays":2,"still":2,"storage":1,"sync":2,"syncing":1,"syncthing":1,"taking":1,"team":1,"ten":1,"tenant":1,"text":1,"that":2,"the":25,"them":4,"theory":1,"there":3,"these":1,"they":4,"this":3,"time":1,"to":7,"tomorrow":1,"tool":3,"tools":2,"trust":1,"truth":1,"under":1,"up":1,"urls":1,"use":1,"useful":1,"user":2,"validation":1,"vault":7,"vaults":2,"viewing":1,"views":1,"violate":1,"way":1,"what":4,"whatever":4,"when":2,"whose":1,"why":1,"wikilink":1,"wikilinks":3,"win":1,"with":4,"without":2,"work":2,"works":2,"write":1,"writes":3,"wrong":1,"wrote":2,"yaml":2,"years":1,"you":13,"your":16,"zero":1,"zetl":15}},{"dl":411,"n":"Wikilinks","s":"concepts/wikilinks","secs":[{"h":"Wikilinks","l":6,"t":"A **wikilink** is how you point from one note to another by name. zetl parses five shapes of wikilink from your Markdown. This page is the syntax reference; for workflow advice see [[Linking Pages]]."},{"h":"The five shapes","l":10,"t":"| Shape | Example | Purpose | |-------|---------|---------| | Plain | `[[Zettelkasten Method]]` | Link to a page; display text is the target name | | Aliased | `[[Zettelkasten Method\\|the slip-box method]]` | Link with custom display text | | Heading | `[[Zettelkasten Method#Origins]]` | Link to a specific heading on a page | | Block | `[[Zettelkasten Method^b3a9f1]]` | Link to a specific content-addressable block | | Embed | `![[Zettelkasten Method]]` | Transclude the target's content inline (see [[Embeds and Transclusion]]) | All five can be combined with a heading or block anchor on the target side: `![[Zettelkasten Method#Origins]]` embeds a single section."},{"h":"How resolution works","l":22,"t":"When you write `[[Some Page]]`, zetl does not look up a URL or an ID. It looks for a file named `Some Page.md` in your vault. Specifically: - **By filename**, not by the `# H1` inside the file. The name in brackets must match the file stem. - **Case-insensitive**. `[[some page]]`, `[[SOME PAGE]]`, and `[[Some Page]]` all resolve to `Some Page.md`. - **Across subfolders**. zetl searches the whole vault tree; you don't write `[[projects/zetl]]`, just `[[zetl]]`. - **Slugified** for the web output — `Some Page.md` becomes `/page/some-page/` in `zetl serve` and `zetl build`. You never need to think about slugs when writing; that's what the resolver is for. If two files share a name across folders, the first match wins and the other is reported as ambiguous by `zetl check`. Rename one."},{"h":"Dead links are fine (until they're not)","l":33,"t":"A wikilink to a page that does not yet exist is a **dead link**. zetl does not crash, does not refuse to render, and does not auto-create the file. It records the edge and surfaces it in: Many people use this as a to-do list: write what you wish existed, then fill in the targets later. See [[Finding Orphans and Dead Links]]."},{"h":"Examples from a real note","l":43,"t":"This note has five outbound edges, one of which is an embed. If `Evergreen Notes.md` doesn't exist yet, the link stays in place; `zetl check` will nag you politely."},{"h":"What wikilinks are not","l":59,"t":"- **Not standard Markdown.** `[[...]]` is an extension. zetl, Obsidian, Logseq, and a few others understand it; vanilla CommonMark does not. If you need to publish with a tool that doesn't, `zetl build` turns wikilinks into regular `<a href>` links for you. - **Not case-sensitive.** Good: no one remembers capitalisation. If you prefer strict matching, name your files consistently. - **Not paths.** `[[Some Page]]` is not a file path. Do not write `[[./notes/Some Page.md]]` — it won't work, and you do not need to."},{"h":"Related","l":65,"t":"- [[Linking Pages]] - [[Embeds and Transclusion]] - [[The Link Graph]] - [[Headings and Blocks]] - [[Finding Orphans and Dead Links]]"}],"tf":{"a":18,"about":1,"across":2,"addressable":1,"advice":1,"aliased":1,"all":2,"ambiguous":1,"an":3,"anchor":1,"and":12,"another":1,"are":2,"as":2,"auto":1,"be":1,"becomes":1,"block":3,"blocks":1,"brackets":1,"by":4,"can":1,"capitalisation":1,"case":2,"combined":1,"commonmark":1,"consistently":1,"content":2,"crash":1,"create":1,"custom":1,"dead":4,"display":2,"do":3,"does":6,"doesn":2,"don":1,"edge":1,"edges":1,"embed":2,"embeds":3,"example":1,"examples":1,"exist":2,"existed":1,"extension":1,"few":1,"file":5,"filename":1,"files":2,"fill":1,"finding":2,"fine":1,"first":1,"five":4,"folders":1,"for":5,"from":3,"good":1,"graph":1,"has":1,"heading":3,"headings":1,"how":2,"id":1,"if":4,"in":6,"inline":1,"insensitive":1,"inside":1,"into":1,"is":9,"it":5,"just":1,"later":1,"link":7,"linking":2,"links":4,"list":1,"logseq":1,"look":1,"looks":1,"many":1,"markdown":2,"match":2,"matching":1,"must":1,"nag":1,"name":5,"named":1,"need":3,"never":1,"no":1,"not":15,"note":3,"obsidian":1,"of":2,"on":2,"one":4,"or":2,"orphans":2,"other":1,"others":1,"outbound":1,"output":1,"page":4,"pages":2,"parses":1,"path":1,"paths":1,"people":1,"place":1,"plain":1,"point":1,"politely":1,"prefer":1,"publish":1,"purpose":1,"re":1,"real":1,"records":1,"reference":1,"refuse":1,"regular":1,"related":1,"remembers":1,"rename":1,"render":1,"reported":1,"resolution":1,"resolve":1,"resolver":1,"s":2,"searches":1,"section":1,"see":3,"sensitive":1,"shape":1,"shapes":2,"share":1,"side":1,"single":1,"slugified":1,"slugs":1,"specific":2,"specifically":1,"standard":1,"stays":1,"stem":1,"strict":1,"subfolders":1,"surfaces":1,"syntax":1,"t":4,"target":3,"targets":1,"text":2,"that":3,"the":19,"then":1,"they":1,"think":1,"this":3,"to":11,"tool":1,"transclude":1,"transclusion":2,"tree":1,"turns":1,"two":1,"understand":1,"until":1,"up":1,"url":1,"use":1,"vanilla":1,"vault":2,"web":1,"what":3,"when":2,"which":1,"whole":1,"wikilink":3,"wikilinks":3,"will":1,"wins":1,"wish":1,"with":3,"won":1,"work":1,"workflow":1,"works":1,"write":4,"writing":1,"yet":2,"you":10,"your":3,"zetl":5}},{"dl":539,"n":"What is SPL","s":"concepts/what-is-spl","secs":[{"h":"What is SPL","l":6,"t":"> **Requires `--features reason` at install time.** See [[Installation]]. **SPL** is **Spindle Lisp** — a small Lisp-family language for writing rules. zetl extracts SPL from your vault and draws conclusions: \"if a project is approved *and* has an owner, it's ready to start\". This page is a gentle orientation; see [[Writing SPL]] for the syntax reference."},{"h":"Why a second language at all","l":12,"t":"Most of your vault is prose — the thing humans are good at reading. But some knowledge is best expressed as a rule, not a paragraph. \"Any release candidate must have a green test run and up-to-date docs\" is easier to write (and check) as a rule than as a checklist you hope to follow. SPL lets you write those rules *inside* your notes, alongside the prose they explain. zetl then reasons over all the SPL in the vault at once — a single combined theory — and tells you what follows."},{"h":"The shape of SPL","l":18,"t":"SPL is parenthesised. Facts and rules are S-expressions: Reading top to bottom: two facts, then a rule named `r-ready` that says *normally*, if both facts hold, conclude `release-candidate`. Run reasoning and zetl will report `+d release-candidate` — defeasibly proved. \"Normally\" is load-bearing. SPL is a **defeasible** logic: a conclusion can be drawn by default, and then withdrawn when stronger contrary evidence appears. See [[What is Defeasible Reasoning]] for what this means and why it matters for knowledge that isn't strictly mathematical."},{"h":"Where SPL lives in your vault","l":35,"t":"Two places. zetl extracts from both and merges them into one theory: 1. **Fenced code blocks** in any Markdown file, tagged `spl`: (given type-safe) (given single-binary) (given fast-startup) 2. **Standalone `.spl` files** anywhere in the vault — useful for rules that are bigger than a code block: Comments start with `;`. Both forms are first-class; mix them however suits your writing. Facts stated in a markdown `spl` block and rules defined in a `.spl` file merge into one theory, so you can keep facts close to the prose that motivated them while the rules live in a shared theory file."},{"h":"What you can ask","l":70,"t":"With SPL in place, a handful of commands become interesting: Each command traces its reasoning back to the file and line where a fact or rule was written — so \"why do you think this release is ready?\" has an answer that points to real notes. See [[Proof Trees]] and [[Running Queries]]."},{"h":"When to reach for SPL","l":84,"t":"SPL is a good fit when: - The knowledge is genuinely rule-shaped, not prose-shaped. - You want a second brain to *check* a conclusion, not just store the notes leading to it. - You care about provenance — you want a conclusion to carry a trail back to its sources. It is **not** a good fit for things Markdown already does well. Don't encode your grocery list in SPL. Don't turn a tag taxonomy into thirty rules. A rule is worth writing when checking it by hand would be tedious or error-prone."},{"h":"Optional, always","l":94,"t":"SPL is feature-gated. If you built zetl without `--features reason`, none of the `zetl reason` commands are available, but the rest of zetl — indexing, linking, viewing, building — works exactly the same. Your `.md` files with ` ```spl ` blocks remain valid Markdown regardless. Nothing locks you in."},{"h":"Related","l":98,"t":"- [[What is Defeasible Reasoning]] - [[Writing SPL]] - [[Running Queries]] - [[Proof Trees]] - [[Installation]]"}],"tf":{"1":1,"2":1,"a":27,"about":1,"all":2,"alongside":1,"already":1,"always":1,"an":2,"and":13,"answer":1,"any":2,"anywhere":1,"appears":1,"approved":1,"are":5,"as":3,"ask":1,"at":4,"available":1,"back":2,"be":2,"bearing":1,"become":1,"best":1,"bigger":1,"block":2,"blocks":2,"both":3,"bottom":1,"brain":1,"building":1,"built":1,"but":2,"by":2,"can":3,"candidate":1,"care":1,"carry":1,"check":2,"checking":1,"checklist":1,"class":1,"close":1,"code":2,"combined":1,"command":1,"commands":2,"comments":1,"conclude":1,"conclusion":3,"conclusions":1,"contrary":1,"date":1,"default":1,"defeasible":3,"defeasibly":1,"defined":1,"do":1,"docs":1,"does":1,"don":2,"drawn":1,"draws":1,"each":1,"easier":1,"encode":1,"error":1,"evidence":1,"exactly":1,"explain":1,"expressed":1,"expressions":1,"extracts":2,"fact":1,"facts":5,"family":1,"feature":1,"fenced":1,"file":4,"files":2,"first":1,"fit":2,"follow":1,"follows":1,"for":7,"forms":1,"from":2,"gated":1,"gentle":1,"genuinely":1,"good":3,"green":1,"grocery":1,"hand":1,"handful":1,"has":2,"have":1,"hold":1,"hope":1,"however":1,"humans":1,"if":3,"in":10,"indexing":1,"inside":1,"install":1,"installation":2,"interesting":1,"into":3,"is":18,"isn":1,"it":5,"its":2,"just":1,"keep":1,"knowledge":3,"language":2,"leading":1,"lets":1,"line":1,"linking":1,"lisp":2,"list":1,"live":1,"lives":1,"load":1,"locks":1,"logic":1,"markdown":4,"mathematical":1,"matters":1,"means":1,"merge":1,"merges":1,"mix":1,"most":1,"motivated":1,"must":1,"named":1,"none":1,"normally":2,"not":4,"notes":3,"nothing":1,"of":5,"once":1,"one":2,"optional":1,"or":2,"orientation":1,"over":1,"owner":1,"page":1,"paragraph":1,"parenthesised":1,"place":1,"places":1,"points":1,"project":1,"prone":1,"proof":2,"prose":4,"proved":1,"provenance":1,"queries":2,"reach":1,"reading":2,"ready":2,"real":1,"reasoning":4,"reasons":1,"reference":1,"regardless":1,"related":1,"release":2,"remain":1,"report":1,"requires":1,"rest":1,"rule":6,"rules":7,"run":2,"running":2,"s":2,"same":1,"says":1,"second":2,"see":4,"shape":1,"shaped":2,"shared":1,"single":1,"small":1,"so":2,"some":1,"sources":1,"spindle":1,"spl":16,"standalone":1,"start":2,"stated":1,"store":1,"strictly":1,"stronger":1,"suits":1,"syntax":1,"t":3,"tag":1,"tagged":1,"taxonomy":1,"tedious":1,"tells":1,"test":1,"than":2,"that":5,"the":15,"them":3,"then":3,"theory":4,"they":1,"thing":1,"things":1,"think":1,"thirty":1,"this":3,"those":1,"time":1,"to":13,"top":1,"traces":1,"trail":1,"trees":2,"turn":1,"two":2,"up":1,"useful":1,"valid":1,"vault":5,"viewing":1,"want":2,"was":1,"well":1,"what":6,"when":4,"where":2,"while":1,"why":3,"will":1,"with":3,"withdrawn":1,"without":1,"works":1,"worth":1,"would":1,"write":2,"writing":5,"written":1,"you":11,"your":7,"zetl":6}},{"dl":473,"n":"Watching for Changes","s":"history/watching-for-changes","secs":[{"h":"Watching for Changes","l":6,"t":"> **Requires `--features history` at install.** See [[Installation]]. `zetl watch` runs continuously, listens for filesystem events in your vault, and takes a silent snapshot on every meaningful change. If you want *every* edit captured — not just the ones you thought to commit — this is the command."},{"h":"The idea","l":12,"t":"When you're writing, you don't want to stop and version your thoughts. You want to keep typing. But later, you'll want to know what you wrote at 3 AM last Tuesday, or recover the paragraph you deleted in a moment of over-zealous editing. `zetl watch` solves this by hooking into the same filesystem events your editor emits on save, debouncing them, and committing a snapshot under `.zetl/jj/`. No dialog, no prompt, no commit message. Just a quiet paper trail."},{"h":"Running it","l":18,"t":"Leave it in a terminal tab (or under `tmux`, `systemd`, `launchd`, whatever you use). It prints a one-line NDJSON event per change by default; pipe to `jq` or `--quiet` it if you don't care."},{"h":"Useful flags","l":31,"t":"| Flag | Default | Purpose | |------|---------|---------| | `--debounce <MS>` | `150` | Coalesce rapid saves (your editor's autosave) into one snapshot. Min 10, max 5000. | | `--exec <CMD>` | — | Run a shell command for each event; event JSON arrives on stdin. | | `--exclude <PATTERN>` | — | Gitignore-syntax pattern, repeatable. Combines with `.zetlignore`. | | `--include-hidden` | off | Also watch `.obsidian/`, `.claude/`, etc. `.git/`, `.zetl/`, `node_modules/` stay excluded. | The `--exec` flag makes `watch` a general-purpose trigger: rebuild a preview, push notifications to another tool, fire a webhook. See [[Lifecycle Hooks]] for structured pre/post-build hooks — `watch --exec` is the lightweight sibling."},{"h":"When to run watch","l":42,"t":"**Good fit:** - Your laptop is your vault. You write notes all day and you want every save preserved. - You're in a research flow and you'll want to time-travel later ([[Time Travel]]). - You're drafting long-form work and a rolling safety net saves you from destructive edits. **Not a good fit:** - CI or batch jobs — snapshots are for human editing sessions, not automation. - Shared vaults on servers where many writers modify files concurrently — use `--collab` mode instead (see [[Running a Team Server]]). - Vaults on network shares with flaky FS events — snapshotting may miss changes or fire spuriously."},{"h":"What actually happens on a snapshot","l":56,"t":"Each coalesced event triggers a silent [jj](https://jj-vcs.github.io/jj/) snapshot inside `.zetl/jj/` — separate from any `.git/` directory you keep alongside. Your `git log` stays clean; your zetl history fills up. See [[Snapshots Under the Hood]] for the full picture. No commit messages, no author prompts, no staging area. The whole thing is designed to stay out of your way while still giving `--at` something to query."},{"h":"Interaction with `zetl index`","l":62,"t":"If you already run `zetl index` as part of a build pipeline, indexing *also* writes a snapshot when history is enabled. `watch` and `index` snapshots coexist: you'll see them interleaved in `zetl history timeline`. Running both is fine — duplicate states are collapsed."},{"h":"Stopping watch","l":66,"t":"`Ctrl-C`. No cleanup needed; the jj store is consistent on every snapshot boundary, not at shutdown."},{"h":"Related","l":70,"t":"- [[Time Travel]] - [[Snapshots Under the Hood]] - [[Page History]] - [[Lifecycle Hooks]]"}],"tf":{"10":1,"3":1,"5000":1,"a":18,"actually":1,"all":1,"alongside":1,"already":1,"also":2,"am":1,"and":7,"another":1,"any":1,"are":2,"area":1,"arrives":1,"as":1,"at":3,"author":1,"automation":1,"autosave":1,"batch":1,"both":1,"boundary":1,"build":2,"but":1,"by":2,"captured":1,"care":1,"change":2,"changes":2,"ci":1,"clean":1,"cleanup":1,"coalesce":1,"coalesced":1,"coexist":1,"collapsed":1,"combines":1,"command":2,"commit":3,"committing":1,"concurrently":1,"consistent":1,"continuously":1,"day":1,"debouncing":1,"default":2,"deleted":1,"designed":1,"destructive":1,"dialog":1,"directory":1,"don":2,"drafting":1,"duplicate":1,"each":2,"edit":1,"editing":2,"editor":2,"edits":1,"emits":1,"enabled":1,"etc":1,"event":4,"events":3,"every":4,"excluded":1,"files":1,"filesystem":2,"fills":1,"fine":1,"fire":2,"fit":2,"flag":2,"flags":1,"flaky":1,"flow":1,"for":6,"form":1,"from":2,"fs":1,"full":1,"general":1,"github":1,"gitignore":1,"giving":1,"good":2,"happens":1,"history":3,"hood":2,"hooking":1,"hooks":3,"https":1,"human":1,"idea":1,"if":3,"in":5,"indexing":1,"inside":1,"install":1,"installation":1,"instead":1,"interaction":1,"interleaved":1,"into":2,"io":1,"is":7,"it":4,"jj":4,"jobs":1,"json":1,"just":2,"keep":2,"know":1,"laptop":1,"last":1,"later":2,"leave":1,"lifecycle":2,"lightweight":1,"line":1,"listens":1,"ll":3,"long":1,"makes":1,"many":1,"max":1,"may":1,"meaningful":1,"message":1,"messages":1,"min":1,"miss":1,"mode":1,"modify":1,"moment":1,"ndjson":1,"needed":1,"net":1,"network":1,"no":7,"not":4,"notes":1,"notifications":1,"of":3,"off":1,"on":7,"one":2,"ones":1,"or":5,"out":1,"over":1,"page":1,"paper":1,"paragraph":1,"part":1,"pattern":1,"per":1,"picture":1,"pipe":1,"pipeline":1,"post":1,"pre":1,"preserved":1,"preview":1,"prints":1,"prompt":1,"prompts":1,"purpose":2,"push":1,"query":1,"quiet":1,"rapid":1,"re":3,"rebuild":1,"recover":1,"related":1,"repeatable":1,"requires":1,"research":1,"rolling":1,"run":3,"running":3,"runs":1,"s":1,"safety":1,"same":1,"save":2,"saves":2,"see":5,"separate":1,"server":1,"servers":1,"sessions":1,"shared":1,"shares":1,"shell":1,"shutdown":1,"sibling":1,"silent":2,"snapshot":7,"snapshots":4,"snapshotting":1,"solves":1,"something":1,"spuriously":1,"staging":1,"states":1,"stay":2,"stays":1,"stdin":1,"still":1,"stop":1,"stopping":1,"store":1,"structured":1,"syntax":1,"t":2,"tab":1,"takes":1,"team":1,"terminal":1,"the":12,"them":2,"thing":1,"this":2,"thought":1,"thoughts":1,"time":3,"to":10,"tool":1,"trail":1,"travel":3,"trigger":1,"triggers":1,"tuesday":1,"typing":1,"under":4,"up":1,"use":2,"useful":1,"vault":2,"vaults":2,"vcs":1,"version":1,"want":6,"watch":3,"watching":1,"way":1,"webhook":1,"what":2,"whatever":1,"when":3,"where":1,"while":1,"whole":1,"with":3,"work":1,"write":1,"writers":1,"writes":1,"writing":1,"wrote":1,"you":19,"your":9,"zealous":1,"zetl":1}},{"dl":453,"n":"Page History","s":"history/page-history","secs":[{"h":"Page History","l":6,"t":"> **Requires `--features history` at install.** See [[Installation]]. Every page in your vault has a life story: when you wrote it, how often you've edited it, when each backlink appeared, how stable it's become. `zetl history page` and the history UI surface that story."},{"h":"The command","l":12,"t":"Prints a reverse-chronological list of snapshots that touched the page, with timestamp, change-ID, and a short description of what changed (links added, links removed, content modified). Defaults to 20 entries; `--limit` goes higher. Case-insensitive on the page name; aliases resolve too."},{"h":"What's available in the web UI","l":27,"t":"When you run `zetl serve` (or publish with `zetl build`) against a history-enabled binary, every rendered page gains a visually-light metadata strip under the title: - **Last changed** — the date the page last received a meaningful edit. - **Stable Nd** — how long it's been untouched. Useful signal for \"this thought has settled\" vs. \"still in flux\". - **history** — a link to the per-page timeline. Opens a list of every snapshot where this page changed, with diffs. The strip disappears on pages with no history (newly created, or vaults without the feature). No empty boxes, no `—` placeholders."},{"h":"Backlinks carry timestamps","l":41,"t":"Each backlink in the sidebar has a `since` field: When did *this particular reference* enter the graph? That's the question `since` answers. If you wrote *Zettelkasten Method* in January and *Spaced Repetition* started linking to it in November, that's a ten-month gap — meaningful signal for how ideas accrete around a note. In templates, the field is `page.backlinks[].since` (RFC 3339 timestamp string, or `null` if the backlink predates the history floor)."},{"h":"Template variables for custom themes","l":54,"t":"If you're customising the look ([[Customising the Look]]), the page context exposes: | Variable | Type | Contents | |----------|------|----------| | `page.history` | object \\| null | `created_at`, `last_changed`, `age_days`, `stable_days`, `link_trend`, `recent_changes` | | `page.history.recent_changes` | array | Most-recent snapshots with timestamp and short delta description | | `page.backlinks[].since` | string \\| null | RFC 3339 timestamp of when each backlink appeared | `page.history` is `null` when zetl was built without history, so templates that read it should use `{% if page.history %}` guards. The default theme's `page-history-meta` block is the reference implementation — copy it or strip it as you like."},{"h":"Why page history matters for writers","l":66,"t":"A note's value often correlates with how it changes over time: - **A page that hasn't moved in 90 days** is either settled or forgotten. Cross-reference with backlink count to know which. - **A page that changes daily** is either active thinking or noisy drift. Look at `recent_changes` to see whether edits are additive or thrashing. - **A page with many late-arriving backlinks** is a concept that earned its keep — other notes grew toward it. Combined with `--at` ([[Time Travel]]), you can reconstruct the arc of an idea: write the first draft, accumulate backlinks, stabilise, cite in a final piece."},{"h":"Related","l":76,"t":"- [[Time Travel]] - [[Watching for Changes]] - [[Snapshots Under the Hood]] - [[Customising the Look]] - [[Backlinks]]"}],"tf":{"20":1,"3339":2,"90":1,"a":17,"accrete":1,"accumulate":1,"active":1,"added":1,"additive":1,"against":1,"aliases":1,"an":1,"and":4,"answers":1,"appeared":2,"arc":1,"are":1,"around":1,"array":1,"arriving":1,"as":1,"at":2,"available":1,"backlink":5,"backlinks":4,"become":1,"been":1,"binary":1,"block":1,"boxes":1,"built":1,"can":1,"carry":1,"case":1,"change":1,"changed":3,"changes":3,"chronological":1,"cite":1,"combined":1,"command":1,"concept":1,"content":1,"contents":1,"context":1,"copy":1,"correlates":1,"count":1,"created":1,"cross":1,"custom":1,"customising":3,"daily":1,"date":1,"days":1,"default":1,"defaults":1,"delta":1,"description":2,"did":1,"diffs":1,"disappears":1,"draft":1,"drift":1,"each":3,"earned":1,"edit":1,"edited":1,"edits":1,"either":2,"empty":1,"enabled":1,"enter":1,"entries":1,"every":3,"exposes":1,"feature":1,"field":2,"final":1,"first":1,"floor":1,"flux":1,"for":5,"forgotten":1,"gains":1,"gap":1,"goes":1,"graph":1,"grew":1,"guards":1,"has":3,"hasn":1,"higher":1,"history":8,"hood":1,"how":5,"id":1,"idea":1,"ideas":1,"if":3,"implementation":1,"in":9,"insensitive":1,"install":1,"installation":1,"is":6,"it":10,"its":1,"january":1,"keep":1,"know":1,"last":2,"late":1,"life":1,"light":1,"like":1,"link":1,"linking":1,"links":2,"list":2,"long":1,"look":4,"many":1,"matters":1,"meaningful":2,"metadata":1,"method":1,"modified":1,"month":1,"most":1,"moved":1,"name":1,"nd":1,"newly":1,"no":3,"noisy":1,"note":2,"notes":1,"november":1,"null":2,"object":1,"of":5,"often":2,"on":2,"opens":1,"or":7,"other":1,"over":1,"page":13,"pages":1,"particular":1,"per":1,"piece":1,"placeholders":1,"predates":1,"prints":1,"publish":1,"question":1,"re":1,"read":1,"received":1,"recent":1,"reconstruct":1,"reference":3,"related":1,"removed":1,"rendered":1,"repetition":1,"requires":1,"resolve":1,"reverse":1,"rfc":2,"run":1,"s":7,"see":2,"settled":2,"short":2,"should":1,"sidebar":1,"signal":2,"snapshot":1,"snapshots":3,"so":1,"spaced":1,"stabilise":1,"stable":2,"started":1,"still":1,"story":2,"string":2,"strip":3,"surface":1,"t":1,"template":1,"templates":2,"ten":1,"that":8,"the":26,"theme":1,"themes":1,"thinking":1,"this":3,"thought":1,"thrashing":1,"time":3,"timeline":1,"timestamp":4,"timestamps":1,"title":1,"to":5,"too":1,"touched":1,"toward":1,"travel":2,"type":1,"ui":2,"under":2,"untouched":1,"use":1,"useful":1,"value":1,"variable":1,"variables":1,"vault":1,"vaults":1,"ve":1,"visually":1,"vs":1,"was":1,"watching":1,"web":1,"what":2,"when":6,"where":1,"whether":1,"which":1,"why":1,"with":9,"without":2,"write":1,"writers":1,"wrote":2,"you":7,"your":1,"zetl":1,"zettelkasten":1}},{"dl":636,"n":"Snapshots Under the Hood","s":"history/snapshots-under-the-hood","secs":[{"h":"Snapshots Under the Hood","l":6,"t":"> **Requires `--features history` at install.** See [[Installation]]. zetl's history feature is backed by [jj](https://jj-vcs.github.io/jj/) (Jujutsu), a VCS that treats the working copy as the commit. This page explains how snapshots actually work, where they live, and how they interact with the rest of zetl."},{"h":"Why jj, not git","l":12,"t":"git is excellent when you want deliberate, human-curated commits with messages and author intent. For a note-taking vault, that ceremony is friction: - You don't want to `git add` your notes. - You don't want to write a commit message every time you fix a typo. - You don't want your `git log` to become unreadable. jj gets out of the way. Every change to the working copy is automatically a new change. There's no staging area. There are no merge conflicts to resolve by hand during normal use. And critically for zetl, jj has a library API (`jj-lib`) that lets a host program drive snapshotting without spawning a subprocess. The tradeoff: jj assumes one user per working copy. For multi-writer scenarios, zetl uses CRDTs ([[Co-editing]]), not jj."},{"h":"Where snapshots live","l":24,"t":"**`.zetl/jj/`**, not `.git/`. This matters: - You can keep your own `.git/` alongside — for publishing, collaborating via pull request, whatever — and zetl's history won't touch it. - Your `.gitignore` should include `.zetl/` (it's generated state). The official examples do. - Backing up `.zetl/jj/` preserves your full history; deleting it removes history without touching your notes."},{"h":"When snapshots happen","l":43,"t":"| Trigger | What happens | |---------|--------------| | `zetl index` | A snapshot is taken before indexing writes the cache. Automatic when history is built in. | | `zetl watch` | Debounced snapshot on each meaningful FS event. See [[Watching for Changes]]. | | Manual `zetl history` | No snapshot — read-only. | | `zetl build` / `zetl serve` | Snapshot via the index step they run; no additional work. | There is no `zetl snapshot` command. Snapshots are always a side effect of something you were doing anyway."},{"h":"Reading snapshots","l":54,"t":"Three subcommands expose the store: And the global `--at <TIME-EXPR>` flag (see [[Time Travel]]) resolves any read-only command to a past snapshot."},{"h":"Graceful absence","l":66,"t":"If your binary was *not* built with `--features history`: - `zetl --at ...` fails with a helpful error pointing at [[Installation]] — not a silent wrong answer. - `zetl history <subcommand>` prints the same error and exits non-zero. - `zetl watch` still runs for filesystem-event emission, but won't snapshot. - `page.history` and `vault.history` template variables are `null` — themes that guard with `{% if page.history %}` render cleanly. - The rendered page metadata strip doesn't appear. No empty `—` placeholders. The degradation is deliberate. You can share a theme, a hook, or a static export between feature builds without breaking anything."},{"h":"`/_history` — the vault timeline","l":78,"t":"When running under `zetl serve` (or after `zetl build`), the URL `/_history` renders a vault-wide recent-changes page: - **Snapshot count** — how many you've accumulated. - **First / latest snapshot dates** — the window of time covered. - **Link-count sparkline** — an inline-SVG trend of total links across time. You can see at a glance when your graph grew most. - **Reverse-chronological list** — up to 50 entries of added / modified / removed pages. The default theme's left rail adds a *Recent changes* link. Strip it by overriding `base.html` in your theme if you want a quieter look."},{"h":"What a snapshot actually contains","l":89,"t":"A jj change, pointing at the full tree of your vault at that instant: - Every `.md` file, byte-exact. - Every file in `.zetl/` *except* the jj store itself (avoiding recursion). - The link graph itself is *not* stored — zetl re-derives it from the files when you query a historical state. This keeps the store small and future-compatible: upgrade zetl's link parser, and past snapshots benefit automatically."},{"h":"Interaction with `zetl index`","l":97,"t":"When history is enabled, `zetl index` implicitly snapshots before writing its cache. The ordering matters: the snapshot captures the pre-index state, so if indexing crashes, the working copy is still recoverable. You can see this in the timeline as an index-triggered entry. If you run `zetl index --no-cache`, the snapshot still happens — `--no-cache` only affects the index-derivation step."},{"h":"Related","l":103,"t":"- [[Time Travel]] - [[Watching for Changes]] - [[Page History]] - [[Installation]] - [[Local-first]]"}],"tf":{"50":1,"a":23,"absence":1,"accumulated":1,"across":1,"actually":2,"added":1,"additional":1,"adds":1,"affects":1,"after":1,"alongside":1,"always":1,"an":2,"and":9,"answer":1,"any":1,"anything":1,"anyway":1,"api":1,"appear":1,"are":3,"area":1,"as":2,"assumes":1,"at":5,"author":1,"automatic":1,"automatically":2,"avoiding":1,"backed":1,"backing":1,"become":1,"before":2,"benefit":1,"between":1,"binary":1,"breaking":1,"builds":1,"built":2,"but":1,"by":3,"byte":1,"cache":2,"can":4,"captures":1,"ceremony":1,"change":3,"changes":4,"chronological":1,"cleanly":1,"co":1,"collaborating":1,"command":2,"commit":2,"commits":1,"compatible":1,"conflicts":1,"contains":1,"copy":4,"count":2,"covered":1,"crashes":1,"crdts":1,"critically":1,"curated":1,"dates":1,"debounced":1,"default":1,"degradation":1,"deleting":1,"deliberate":2,"derivation":1,"derives":1,"do":1,"doesn":1,"doing":1,"don":3,"drive":1,"during":1,"each":1,"editing":1,"effect":1,"emission":1,"empty":1,"enabled":1,"entries":1,"entry":1,"error":2,"event":2,"every":4,"exact":1,"examples":1,"excellent":1,"except":1,"exits":1,"explains":1,"export":1,"expose":1,"fails":1,"feature":2,"file":2,"files":1,"filesystem":1,"first":2,"fix":1,"flag":1,"for":7,"friction":1,"from":1,"fs":1,"full":2,"future":1,"generated":1,"gets":1,"git":2,"github":1,"glance":1,"global":1,"graceful":1,"graph":2,"grew":1,"guard":1,"hand":1,"happen":1,"happens":2,"has":1,"helpful":1,"historical":1,"history":7,"hood":1,"hook":1,"host":1,"how":3,"https":1,"human":1,"if":4,"implicitly":1,"in":4,"include":1,"index":4,"indexing":2,"inline":1,"install":1,"installation":3,"instant":1,"intent":1,"interact":1,"interaction":1,"io":1,"is":11,"it":5,"its":1,"itself":2,"jj":10,"jujutsu":1,"keep":1,"keeps":1,"latest":1,"left":1,"lets":1,"library":1,"link":4,"links":1,"list":1,"live":2,"local":1,"look":1,"manual":1,"many":1,"matters":2,"meaningful":1,"merge":1,"message":1,"messages":1,"metadata":1,"modified":1,"most":1,"multi":1,"new":1,"no":6,"non":1,"normal":1,"not":6,"note":1,"notes":2,"of":7,"official":1,"on":1,"one":1,"only":3,"or":2,"ordering":1,"out":1,"overriding":1,"own":1,"page":4,"pages":1,"parser":1,"past":2,"per":1,"placeholders":1,"pointing":2,"pre":1,"preserves":1,"prints":1,"program":1,"publishing":1,"pull":1,"query":1,"quieter":1,"rail":1,"re":1,"read":2,"reading":1,"recent":2,"recoverable":1,"recursion":1,"related":1,"removed":1,"removes":1,"render":1,"rendered":1,"renders":1,"request":1,"requires":1,"resolve":1,"resolves":1,"rest":1,"reverse":1,"run":2,"running":1,"runs":1,"s":6,"same":1,"scenarios":1,"see":5,"share":1,"should":1,"side":1,"silent":1,"small":1,"snapshot":11,"snapshots":8,"snapshotting":1,"so":1,"something":1,"sparkline":1,"spawning":1,"staging":1,"state":3,"static":1,"step":2,"still":3,"store":3,"stored":1,"strip":2,"subcommands":1,"subprocess":1,"svg":1,"t":6,"taken":1,"taking":1,"template":1,"that":5,"the":31,"theme":3,"themes":1,"there":3,"they":3,"this":4,"three":1,"time":5,"timeline":2,"to":7,"total":1,"touch":1,"touching":1,"tradeoff":1,"travel":2,"treats":1,"tree":1,"trend":1,"trigger":1,"triggered":1,"typo":1,"under":2,"unreadable":1,"up":2,"upgrade":1,"url":1,"use":1,"user":1,"uses":1,"variables":1,"vault":4,"vcs":2,"ve":1,"via":2,"want":5,"was":1,"watching":2,"way":1,"were":1,"what":2,"whatever":1,"when":7,"where":2,"why":1,"wide":1,"window":1,"with":6,"without":3,"won":2,"work":2,"working":4,"write":1,"writer":1,"writes":1,"writing":1,"wrong":1,"you":14,"your":10,"zero":1,"zetl":7}},{"dl":431,"n":"Time Travel","s":"history/time-travel","secs":[{"h":"Time Travel","l":6,"t":"> **Requires `--features history` at install.** See [[Installation]]. `--at` is a global flag that re-runs any read-only zetl command against a past state of your vault. Last Tuesday's backlinks, the orphan list from before the big refactor, the link count on the day you published — all one flag away."},{"h":"Why time-travel your notes","l":12,"t":"You remember writing about *Glass-Steagall* in the essay you posted in June, but the page doesn't mention it any more. Did you delete the link, or did you never write it? Without history, you'd dig through git log (if you even use git on your notes) and eyeball old files. With `--at`: The graph as it stood that day. Same output format, same flags, same wikilink resolver — just a different snapshot under the hood. Other uses: - **Audit a stable conclusion.** `zetl --at \"last monday\" check` to confirm the vault was clean when you claimed it was. - **Recover a deleted page.** `zetl --at HEAD~5 view \"Lost Thought\"` shows the full rendered page from five snapshots ago. - **Compare statistics across time.** `zetl --at \"3 months ago\" stats` against `zetl stats` — a graph-size delta without leaving the shell. - **Retrace a thought.** When did I first link *Zettelkasten Method* to *Spaced Repetition*? `zetl --at \"90 days ago\" backlinks \"Spaced Repetition\"`."},{"h":"What `--at` accepts","l":29,"t":"| Expression | Example | Notes | |------------|---------|-------| | ISO 8601 date | `--at \"2024-01-15\"` | Resolves to the latest snapshot on or before that date. | | Natural-language relative | `--at \"3 days ago\"` | Also `\"last monday\"`, `\"yesterday\"`, `\"2 weeks ago\"`. | | VCS ref | `--at HEAD~1` | One snapshot before the current tip. | | Change-ID prefix | `--at abc12` | jj change-ID, any unique prefix. | Quote expressions that contain spaces — your shell will thank you."},{"h":"Which commands accept it","l":40,"t":"Every read-only subcommand. A partial list: Commands that *write* — `index`, `watch` with side effects, hooks that mutate files — ignore `--at` or refuse to run against a historical state. You cannot accidentally edit a past vault."},{"h":"Typical workflow","l":55,"t":"1. `zetl stats` — current numbers. 2. `zetl --at \"last monday\" stats` — a week ago. 3. Eye the diff. If something's interesting, narrow: `zetl --at \"last monday\" links \"That Page\"`. 4. Still interesting? `zetl diff --from \"last monday\"` summarises the full graph-level delta. No checkout, no stash, no branch — the live vault on disk doesn't move."},{"h":"Use case: \"what did the vault look like when I wrote that blog post?\"","l":64,"t":"You posted an essay on March 3rd. The essay cites five zetl pages. You want to know what *those pages* said on March 3rd — not their current, evolved state. The view reads from that snapshot's content; the link graph matches. Export the essay's sources as they appeared at publication time, so the post's argument stays reproducible."},{"h":"When `--at` returns nothing useful","l":75,"t":"If you installed history late in the vault's life, snapshots only exist from that point forward. `zetl history timeline` lists every snapshot on record — your effective floor for time-travel."},{"h":"Related","l":79,"t":"- [[Watching for Changes]] - [[Page History]] - [[Snapshots Under the Hood]] - [[CLI Overview]]"}],"tf":{"1":1,"2":1,"3":1,"3rd":2,"4":1,"8601":1,"a":11,"about":1,"accept":1,"accepts":1,"accidentally":1,"across":1,"against":3,"ago":2,"all":1,"also":1,"an":1,"and":1,"any":3,"appeared":1,"argument":1,"as":2,"at":2,"audit":1,"away":1,"backlinks":1,"before":3,"big":1,"blog":1,"branch":1,"but":1,"cannot":1,"case":1,"change":2,"changes":1,"checkout":1,"cites":1,"claimed":1,"clean":1,"cli":1,"command":1,"commands":2,"compare":1,"conclusion":1,"confirm":1,"contain":1,"content":1,"count":1,"current":3,"d":1,"date":2,"day":2,"delete":1,"deleted":1,"delta":2,"did":4,"diff":1,"different":1,"dig":1,"disk":1,"doesn":2,"edit":1,"effective":1,"effects":1,"essay":4,"even":1,"every":2,"evolved":1,"example":1,"exist":1,"export":1,"expression":1,"expressions":1,"eye":1,"eyeball":1,"files":2,"first":1,"five":2,"flag":2,"flags":1,"floor":1,"for":2,"format":1,"forward":1,"from":4,"full":2,"git":2,"glass":1,"global":1,"graph":4,"historical":1,"history":3,"hood":2,"hooks":1,"i":2,"id":2,"if":3,"ignore":1,"in":3,"install":1,"installation":1,"installed":1,"interesting":2,"is":1,"iso":1,"it":5,"jj":1,"june":1,"just":1,"know":1,"language":1,"last":1,"late":1,"latest":1,"leaving":1,"level":1,"life":1,"like":1,"link":4,"list":2,"lists":1,"live":1,"log":1,"look":1,"march":2,"matches":1,"mention":1,"method":1,"more":1,"move":1,"mutate":1,"narrow":1,"natural":1,"never":1,"no":3,"not":1,"notes":3,"nothing":1,"numbers":1,"of":1,"old":1,"on":7,"one":2,"only":3,"or":3,"orphan":1,"other":1,"output":1,"overview":1,"page":4,"pages":2,"partial":1,"past":2,"point":1,"post":2,"posted":2,"prefix":2,"publication":1,"published":1,"quote":1,"re":1,"read":2,"reads":1,"record":1,"recover":1,"ref":1,"refactor":1,"refuse":1,"related":1,"relative":1,"remember":1,"rendered":1,"repetition":1,"reproducible":1,"requires":1,"resolver":1,"resolves":1,"retrace":1,"returns":1,"run":1,"runs":1,"s":6,"said":1,"same":3,"see":1,"shell":2,"shows":1,"side":1,"size":1,"snapshot":5,"snapshots":3,"so":1,"something":1,"sources":1,"spaced":1,"spaces":1,"stable":1,"stash":1,"state":3,"statistics":1,"stays":1,"steagall":1,"still":1,"stood":1,"subcommand":1,"summarises":1,"t":2,"thank":1,"that":9,"the":25,"their":1,"they":1,"those":1,"thought":1,"through":1,"time":5,"tip":1,"to":5,"travel":3,"tuesday":1,"typical":1,"under":2,"unique":1,"use":2,"useful":1,"uses":1,"vault":6,"vcs":1,"view":1,"want":1,"was":2,"watching":1,"week":1,"what":3,"when":4,"which":1,"why":1,"wikilink":1,"will":1,"with":2,"without":2,"workflow":1,"write":2,"writing":1,"wrote":1,"you":13,"your":5,"zetl":2,"zettelkasten":1}},{"dl":540,"n":"MCP Server","s":"automation/mcp-server","secs":[{"h":"MCP Server","l":6,"t":"Expose your vault to AI agents as typed [Model Context Protocol](https://modelcontextprotocol.io) tools. Claude Desktop, Cursor, or any MCP-aware agent can then search pages, traverse backlinks, resolve [[Wikilinks]], ask for similar pages, and run [[Running Queries|reasoning queries]] — without giving up control of the underlying files. > **Requires `--features mcp` at install time.** See [[Installation]]."},{"h":"Why","l":12,"t":"Agents need structured access to your knowledge. Dumping a vault into a context window loses the graph; scraping files on disk loses the reasoning layer. MCP gives the agent a *typed tool surface* — `search`, `get_page`, `links`, `backlinks`, `path`, `similar`, `check`, `status`, `reason` — backed by the same index zetl uses itself. You stay local-first; the agent never touches your files directly."},{"h":"Transports","l":16,"t":"`--transport stdio` is the default. HTTP binds to `127.0.0.1` by default; pass `--insecure` to allow a non-loopback bind (don't do this without a reverse proxy and a delegate token — see below). `--cors-origin https://agent.example.com` scopes the browser-side surface."},{"h":"The available tools","l":28,"t":"| Tool | What it does | |------|--------------| | `search` | Full-text search across the vault | | `get_page` | Fetch a page by name — Markdown + frontmatter | | `links` | List forward links from a page | | `backlinks` | List pages pointing at a page | | `path` | Shortest path between two pages in the link graph | | `similar` | SimHash-nearest pages (see [[Similar Pages]]) | | `check` | Run `zetl check` — dead links, orphans | | `status` | Reasoning conclusions for a page (requires `--features reason`) | | `reason` | Evaluate an SPL query (requires `--features reason`) | The reasoning tools exist only when zetl was built with both `mcp` and `reason` features — see [[What is Defeasible Reasoning]] for what they return."},{"h":"Delegate tokens","l":44,"t":"An agent shouldn't get your unscoped identity. zetl issues user-signed JWTs — **delegate tokens** — that scope access per-tool and per-page glob: `--save-key` writes the derived signing key to `~/.config/zetl/identity.key` so later `delegate` runs don't need the mnemonic. The server verifies tokens against the issuing identity and rejects anything missing the claimed scope or expired. `--allowed-issuer <DID>` on the `mcp` command restricts which identities may issue tokens for that server — the right knob for a shared HTTP deployment."},{"h":"Claude Desktop config","l":67,"t":"Drop this into `claude_desktop_config.json`: Claude Desktop spawns one stdio subprocess per MCP server it knows about, so the agent has live access the whole time the chat is open. Restart Claude Desktop after editing the config. The zetl server logs to stderr — visible in the Desktop log pane if a tool call misbehaves."},{"h":"Cursor / other IDEs","l":84,"t":"Any MCP-aware client that takes a `command` + `args` pair works the same way. Point the config at the `zetl` binary with `-d <vault>` and `mcp`; the client handles transport. For HTTP clients, configure the token header with the output of `zetl delegate`."},{"h":"What the agent sees","l":88,"t":"Tools are typed. `search` takes a query string and an optional result limit; `get_page` takes a page name and returns structured `{frontmatter, content, path}`. The agent's tool-use loop can chain calls — `search` for a term, `backlinks` on the top result, `path` between that and a reference note — without ever seeing a raw filesystem path it could mis-interpret. Writes are not exposed. The MCP surface is read-only. Agents that need to *create* pages should use the [[Web Server|zetl serve]] API, which has proper auth and CRDT-aware saves."},{"h":"Related","l":94,"t":"- [[Lifecycle Hooks]] — the `on-agent` hook fires on every MCP tool call; use it for audit logging. - [[Running a Team Server]] — sharing a vault across users; MCP plays well with the same collab identity keys. - [[Capability URLs]] — the static-site analogue for sharing without a running server. - [[Installation]] — `cargo install --features mcp`. - [[Running Queries]] — what the `reason` tool evaluates."}],"tf":{"a":21,"about":1,"access":3,"across":2,"after":1,"against":1,"agent":7,"agents":3,"ai":1,"allow":1,"an":3,"analogue":1,"and":10,"any":2,"anything":1,"api":1,"are":2,"as":1,"ask":1,"at":3,"audit":1,"auth":1,"available":1,"aware":3,"backed":1,"backlinks":1,"below":1,"between":2,"binary":1,"bind":1,"binds":1,"both":1,"browser":1,"built":1,"by":3,"call":2,"calls":1,"can":2,"capability":1,"chain":1,"chat":1,"claimed":1,"claude":4,"client":2,"clients":1,"collab":1,"command":1,"conclusions":1,"config":3,"configure":1,"context":2,"control":1,"could":1,"crdt":1,"create":1,"cursor":2,"dead":1,"default":2,"defeasible":1,"delegate":3,"deployment":1,"derived":1,"desktop":5,"directly":1,"disk":1,"do":1,"does":1,"don":2,"drop":1,"dumping":1,"editing":1,"evaluate":1,"evaluates":1,"ever":1,"every":1,"exist":1,"expired":1,"expose":1,"exposed":1,"features":1,"fetch":1,"files":3,"filesystem":1,"fires":1,"first":1,"for":9,"forward":1,"from":1,"frontmatter":1,"full":1,"get":1,"gives":1,"giving":1,"glob":1,"graph":2,"handles":1,"has":2,"header":1,"hook":1,"hooks":1,"http":3,"https":1,"identities":1,"identity":3,"ides":1,"if":1,"in":2,"index":1,"install":1,"installation":2,"interpret":1,"into":2,"io":1,"is":4,"issue":1,"issues":1,"issuing":1,"it":4,"itself":1,"jwts":1,"key":1,"keys":1,"knob":1,"knowledge":1,"knows":1,"later":1,"layer":1,"lifecycle":1,"limit":1,"link":1,"links":2,"list":2,"live":1,"local":1,"log":1,"logging":1,"logs":1,"loop":1,"loopback":1,"loses":2,"markdown":1,"may":1,"mcp":8,"mis":1,"misbehaves":1,"missing":1,"mnemonic":1,"model":1,"modelcontextprotocol":1,"name":2,"nearest":1,"need":3,"never":1,"non":1,"not":1,"note":1,"of":2,"on":4,"one":1,"only":2,"open":1,"optional":1,"or":2,"orphans":1,"other":1,"output":1,"page":6,"pages":7,"pair":1,"pane":1,"pass":1,"path":2,"per":3,"plays":1,"point":1,"pointing":1,"proper":1,"protocol":1,"proxy":1,"queries":3,"query":2,"raw":1,"read":1,"reasoning":5,"reference":1,"rejects":1,"related":1,"requires":3,"resolve":1,"restart":1,"restricts":1,"result":2,"return":1,"returns":1,"reverse":1,"right":1,"run":2,"running":4,"runs":1,"s":1,"same":3,"saves":1,"scope":2,"scopes":1,"scraping":1,"search":2,"see":4,"seeing":1,"sees":1,"serve":1,"server":8,"shared":1,"sharing":2,"shortest":1,"should":1,"shouldn":1,"side":1,"signed":1,"signing":1,"simhash":1,"similar":2,"site":1,"so":2,"spawns":1,"spl":1,"static":1,"stay":1,"stderr":1,"stdio":1,"string":1,"structured":2,"subprocess":1,"surface":3,"t":3,"takes":3,"team":1,"term":1,"text":1,"that":5,"the":40,"then":1,"they":1,"this":2,"time":2,"to":7,"token":2,"tokens":4,"tool":7,"tools":4,"top":1,"touches":1,"transport":1,"transports":1,"traverse":1,"two":1,"typed":3,"underlying":1,"unscoped":1,"up":1,"urls":1,"use":3,"user":1,"users":1,"uses":1,"vault":4,"verifies":1,"visible":1,"was":1,"way":1,"web":1,"well":1,"what":5,"when":1,"which":2,"whole":1,"why":1,"wikilinks":1,"window":1,"with":4,"without":4,"works":1,"writes":2,"you":1,"your":4,"zetl":5}},{"dl":635,"n":"Plugin Ecosystems","s":"automation/plugin-ecosystems","secs":[{"h":"Plugin Ecosystems","l":6,"t":"Rather than rewriting every transform in zetl, reuse the ones that already exist. The plugin-ecosystem adapters let a [[Render Pipeline Hooks|render-pipeline hook]] delegate to a **Pandoc filter**, an **mdBook preprocessor**, or a **remark plugin**. zetl handles AST translation in both directions; the hook manifest just names the plugin. If you already have a Lua filter you wrote for an academic paper, or an `mdbook-mermaid` you use for a book, you can reach for it from a zetl vault without a rewrite."},{"h":"Declaring an ecosystem hook","l":12,"t":"Pick the stage, name the ecosystem, point at the plugin. A Pandoc Lua filter in the transform stage: Each ecosystem has its own required/optional manifest fields: | Ecosystem | Required | Optional | |-----------|----------|----------| | `pandoc`  | `exec` **or** `lua_filter` | `args` | | `mdbook`  | `exec`   | `scope = \"page\" \\| \"vault\"` | | `remark`  | `package` | `version`, `options` | Pandoc hooks default to the `transform` stage; mdBook preprocessors run at `pre-parse` (they operate on raw Markdown); remark targets the `transform` stage with the mdast AST type. See each ecosystem's deep-dive guide in the main repo's `docs/ecosystems/` directory for translation details and per-plugin quirks."},{"h":"Scaffolding","l":36,"t":"The authoring CLI seeds the right manifest fields (and, for Pandoc, drops a starter identity Lua filter on disk) when you pass `--ecosystem`: The generated hook works out of the box: `zetl hook test smallcaps` will pass against the seeded fixture. From there you edit the filter, add selectors, and tune the behavioural contract like any other [[Render Pipeline Hooks|pipeline hook]]."},{"h":"Probing ecosystems","l":48,"t":"Before a build — especially in CI — check every configured ecosystem is reachable: Exit code 0 when every *configured* ecosystem is available; the zero-configured state also exits 0. Missing-runtime cases surface hints pointing at the install path for the tool (`brew install pandoc`, `cargo install mdbook`, etc.). `--json` is the same output forced to JSON for piping into CI gates."},{"h":"Feature flags","l":68,"t":"Each adapter lives behind a cargo feature: - `ecosystem-pandoc` - `ecosystem-mdbook` - `ecosystem-remark` All three are compiled into default release builds. If you want a minimal binary — embedded deployment, a containerised collab server with no Pandoc on disk — build with `cargo install --no-default-features --features \"<your flags>\"` and include only the ecosystems you need. A hook declaring `ecosystem = \"pandoc\"` against a binary compiled without that feature fails fast with a `RuntimeAbsence` diagnostic pointing at the ecosystem matrix entry — you won't get cryptic \"exec not found\" mysteries."},{"h":"Mixed-parser diagnostic","l":80,"t":"zetl's native parser is CommonMark. Pandoc has its own, richer parser; remark's mdast is different again. Most of the time this doesn't matter — the adapter translates at the boundary — but if a page parsed by CommonMark is routed through a hook expecting Pandoc AST (or vice versa), you'll see a **five-part mixed-parser diagnostic** with three concrete remediations: 1. Set `parser:` in the page's frontmatter to force the parser. 2. Narrow the hook's selector so it doesn't match incompatible pages. 3. Disable the hook for that page (frontmatter opt-out). By default the diagnostic is a warning and the build continues. Pass `zetl build --strict-parsers` to upgrade it to a fatal error — the right choice for CI."},{"h":"Per-ecosystem guides","l":90,"t":"The deep-dive guides in the zetl repo's `docs/ecosystems/` cover install, the specific manifest fields, and the compatibility matrix of known-working plugins: - [pandoc.md](https://codeberg.org/anuna/zetl/src/branch/main/docs/ecosystems/pandoc.md) — filter vs. native mode, Quarto support. - [mdbook.md](https://codeberg.org/anuna/zetl/src/branch/main/docs/ecosystems/mdbook.md) — envelope shape, scope settings. - [remark.md](https://codeberg.org/anuna/zetl/src/branch/main/docs/ecosystems/remark.md) — harness architecture, package resolution. - [matrix-contribution.md](https://codeberg.org/anuna/zetl/src/branch/main/docs/ecosystems/matrix-contribution.md) — how to file a known-working plugin."},{"h":"Related","l":99,"t":"- [[Render Pipeline Hooks]] — the underlying hook mechanism that ecosystem adapters plug into. - [[Lifecycle Hooks]] — the around-the-build sibling for shell scripts that don't need AST access. - [[Customising the Look]] — how a theme can bundle an ecosystem hook. - [[CLI Overview]] — every `zetl hook` and `zetl ecosystem` subcommand in one reference. - [[Installation]] — cargo feature flags at install time."}],"tf":{"0":2,"1":1,"2":1,"3":1,"a":23,"academic":1,"access":1,"adapter":2,"adapters":2,"add":1,"again":1,"against":2,"all":1,"already":2,"also":1,"an":5,"and":7,"anuna":4,"any":1,"architecture":1,"are":1,"around":1,"ast":4,"at":6,"authoring":1,"available":1,"before":1,"behavioural":1,"behind":1,"binary":2,"book":1,"both":1,"boundary":1,"box":1,"branch":4,"build":4,"builds":1,"bundle":1,"but":1,"by":2,"can":2,"cargo":2,"cases":1,"check":1,"choice":1,"ci":3,"cli":2,"code":1,"codeberg":4,"collab":1,"commonmark":2,"compatibility":1,"compiled":2,"concrete":1,"configured":3,"containerised":1,"continues":1,"contract":1,"contribution":2,"cover":1,"cryptic":1,"customising":1,"declaring":2,"deep":2,"default":3,"delegate":1,"deployment":1,"details":1,"diagnostic":4,"different":1,"directions":1,"directory":1,"disable":1,"disk":2,"dive":2,"docs":4,"doesn":2,"don":1,"drops":1,"each":3,"ecosystem":12,"ecosystems":7,"edit":1,"embedded":1,"entry":1,"envelope":1,"error":1,"especially":1,"etc":1,"every":4,"exec":1,"exist":1,"exit":1,"exits":1,"expecting":1,"fails":1,"fast":1,"fatal":1,"feature":4,"fields":3,"file":1,"filter":6,"five":1,"fixture":1,"flags":2,"for":10,"force":1,"forced":1,"found":1,"from":2,"frontmatter":2,"gates":1,"generated":1,"get":1,"guide":1,"guides":2,"handles":1,"harness":1,"has":2,"have":1,"hints":1,"hook":11,"hooks":5,"how":2,"https":4,"identity":1,"if":3,"in":8,"include":1,"incompatible":1,"install":3,"installation":1,"into":3,"is":7,"it":3,"its":2,"json":1,"just":1,"known":2,"let":1,"lifecycle":1,"like":1,"lives":1,"ll":1,"look":1,"lua":3,"main":5,"manifest":4,"markdown":1,"match":1,"matrix":4,"matter":1,"md":8,"mdast":2,"mdbook":4,"mechanism":1,"minimal":1,"missing":1,"mixed":2,"mode":1,"most":1,"mysteries":1,"name":1,"names":1,"narrow":1,"native":2,"need":2,"no":1,"not":1,"of":3,"on":3,"one":1,"ones":1,"only":1,"operate":1,"opt":1,"optional":2,"or":4,"org":4,"other":1,"out":2,"output":1,"overview":1,"own":2,"package":1,"page":3,"pages":1,"pandoc":9,"paper":1,"parsed":1,"parser":5,"part":1,"pass":3,"path":1,"per":2,"pick":1,"pipeline":5,"piping":1,"plug":1,"plugin":7,"plugins":1,"point":1,"pointing":2,"preprocessor":1,"preprocessors":1,"probing":1,"quarto":1,"quirks":1,"rather":1,"raw":1,"reach":1,"reachable":1,"reference":1,"related":1,"release":1,"remark":5,"remediations":1,"render":4,"repo":2,"required":2,"resolution":1,"reuse":1,"rewrite":1,"rewriting":1,"richer":1,"right":2,"routed":1,"run":1,"runtime":1,"s":7,"same":1,"scaffolding":1,"scope":1,"scripts":1,"see":2,"seeded":1,"seeds":1,"selector":1,"selectors":1,"server":1,"set":1,"settings":1,"shape":1,"shell":1,"sibling":1,"so":1,"specific":1,"src":4,"stage":4,"starter":1,"state":1,"subcommand":1,"support":1,"surface":1,"t":4,"targets":1,"than":1,"that":5,"the":43,"theme":1,"there":1,"they":1,"this":1,"three":2,"through":1,"time":2,"to":7,"tool":1,"transform":2,"translates":1,"translation":2,"tune":1,"type":1,"underlying":1,"upgrade":1,"use":1,"vault":1,"versa":1,"vice":1,"vs":1,"want":1,"warning":1,"when":2,"will":1,"with":5,"without":2,"won":1,"working":2,"works":1,"wrote":1,"you":10,"zero":1,"zetl":9}},{"dl":652,"n":"Render Pipeline Hooks","s":"automation/render-pipeline-hooks","secs":[{"h":"Render Pipeline Hooks","l":6,"t":"Transform a page *mid-build* by mutating a typed AST rather than its serialised form. Unlike the around-the-build [[Lifecycle Hooks]], render-pipeline hooks run on every matching page as it moves through the renderer — callouts, custom blocks, link rewrites, banner injection, citation expansion, anything that needs to touch structure. Hooks live under `.zetl/hooks/<stage>.d/` (or `.zetl/themes/<name>/hooks/<stage>.d/` for theme-bundled ones) and stay resident for the duration of a build via a JSON-lines protocol over stdin/stdout. No per-page subprocess spawn, no shell fork on every paragraph."},{"h":"The three stages","l":12,"t":"| Stage | Payload at the boundary | Typical use | |-------|-------------------------|-------------| | `pre-parse` | raw Markdown string | Frontmatter rewrites, include/import expansion, prelude injection | | `transform` | typed AST (`zetl-ast` schema v1) | Callouts, custom blocks, link-graph mutations, ecosystem plugins | | `post-render` | HTML fragment string | Banner injection, post-processing, accessibility fixes | Stages run in that order, once per page. A `pre-parse` hook sees what the author wrote; a `transform` hook sees the parsed tree; a `post-render` hook sees the nearly-final HTML."},{"h":"A hook is two files","l":22,"t":"Scaffold one with the authoring CLI — it writes the skeleton, a sidecar manifest, and a seeded fixture so `zetl hook test` passes immediately: Resulting layout: The sidecar TOML manifest is where all the interesting configuration lives: Selectors narrow *which* pages this hook applies to: a glob, a frontmatter predicate, and an optional content regex (prefix with `re:`). Only pages matching all three are dispatched. Contracts are declared behaviour the pipeline then **enforces** — more on that below."},{"h":"Authoring workflow","l":68,"t":"`hook dry-run` is the fast feedback loop for selectors. `hook capabilities` is how you catch a hook that's declaring an AST schema version the running zetl binary can't speak. `hook coverage` tells you whether the hook actually *did* anything the last time you built."},{"h":"The AST","l":92,"t":"The transform stage gets a parsed `Document` matching `zetl-ast` schema v1. The full node list, JSON shape, and rendering contract are in the [zetl-ast reference](https://codeberg.org/anuna/zetl/src/branch/main/docs/zetl-ast-reference.md) — every node type (Paragraph, Heading, Wikilink, Embed, SplBlock, etc.) with canonical examples. Don't duplicate that content in your hook; just import the helper library and walk the tree. Helper libraries ship alongside the runtime so hooks don't hand-roll JSON framing: - **Python** — `tools/zetl-ast-py/`, py3.9+, `walk()`, `map_nodes()`, `@on_node` decorator. - **TypeScript** — `tools/zetl-ast-js/`, typed AST classes, `onNode()` dispatch table. Inspect or diff AST directly with:"},{"h":"Behavioural contracts","l":108,"t":"The `[contract]` block is not just documentation — zetl enforces it: - `preserves = [\"Wikilink\", \"Embed\"]` — counts these node types pre- and post-hook; a strict decrease fails the hook. - `idempotent = true` — the pipeline runs the hook a second time on its own output; if the result differs, it fails. - `may_restructure = true` — allowed only at `pre-parse`; denied at `transform`/`post-render`. - `expansion_bound = 2.0` — output byte size ratio ceiling. A violation surfaces as a five-part diagnostic — *summary, context, observed, cause, hint* — with concrete remediations. Noisy hooks with loose contracts are easy to audit because the diagnostic names the exact node type that went missing."},{"h":"When a hook fails","l":119,"t":"The pipeline no longer aborts. When a hook errors mid-stage, the page reverts to the *previous stage's output* and the pipeline keeps going. The failure is appended to `diagnostics.json` in the build output as a `FailureRecord` with the hook, stage, page, reason, and duration. The rest of your vault builds normally. Use `zetl hook coverage` after the build to see which hooks failed on which pages."},{"h":"Safe mode","l":125,"t":"`zetl build --safe-mode` and `zetl serve --safe-mode` skip every vault hook. Only hooks declared in the active theme's `[[theme.hooks]]` manifest run. That's the switch for previewing an untrusted vault or auditing a theme release. Full security model — env-var redaction, stdout/stderr caps, per-hook timeouts — is documented in the [hook-security guide](https://codeberg.org/anuna/zetl/src/branch/main/docs/hook-security.md)."},{"h":"Related","l":129,"t":"- [[Lifecycle Hooks]] — hooks that fire *around* the build, not inside it. - [[Plugin Ecosystems]] — delegate transforms to Pandoc filters, mdBook preprocessors, or remark plugins. - [[MCP Server]] — expose the vault to AI agents once your hooks have shaped it. - [[Customising the Look]] — theme hooks and template overrides side by side. - [[Configuration]] — the `.zetl/hooks/` directory, theme manifest keys, and `ZETL_*` env vars."}],"tf":{"9":1,"a":22,"aborts":1,"accessibility":1,"active":1,"actually":1,"after":1,"agents":1,"ai":1,"all":2,"allowed":1,"alongside":1,"an":3,"and":11,"anuna":2,"anything":2,"appended":1,"applies":1,"are":4,"around":2,"as":3,"ast":8,"at":3,"audit":1,"auditing":1,"author":1,"authoring":2,"banner":2,"because":1,"behaviour":1,"behavioural":1,"below":1,"binary":1,"block":1,"blocks":2,"boundary":1,"branch":2,"build":6,"builds":1,"built":1,"bundled":1,"by":2,"byte":1,"callouts":2,"can":1,"canonical":1,"caps":1,"catch":1,"cause":1,"ceiling":1,"citation":1,"classes":1,"cli":1,"codeberg":2,"concrete":1,"configuration":2,"content":2,"context":1,"contract":1,"contracts":3,"counts":1,"custom":2,"customising":1,"declared":2,"declaring":1,"decorator":1,"decrease":1,"delegate":1,"denied":1,"diagnostic":2,"did":1,"diff":1,"differs":1,"directly":1,"directory":1,"dispatch":1,"dispatched":1,"docs":2,"documentation":1,"documented":1,"don":2,"duplicate":1,"duration":2,"easy":1,"ecosystem":1,"ecosystems":1,"embed":1,"enforces":2,"env":2,"errors":1,"etc":1,"every":4,"exact":1,"examples":1,"expansion":2,"expose":1,"failed":1,"fails":3,"failure":1,"fast":1,"feedback":1,"files":1,"filters":1,"final":1,"fire":1,"five":1,"fixes":1,"fixture":1,"for":4,"fork":1,"form":1,"fragment":1,"framing":1,"frontmatter":2,"full":2,"gets":1,"glob":1,"going":1,"graph":1,"guide":1,"hand":1,"have":1,"heading":1,"helper":2,"hint":1,"hook":18,"hooks":12,"how":1,"html":2,"https":2,"if":1,"immediately":1,"import":2,"in":6,"include":1,"injection":3,"inside":1,"inspect":1,"interesting":1,"is":7,"it":6,"its":2,"json":3,"just":2,"keeps":1,"keys":1,"last":1,"layout":1,"libraries":1,"library":1,"lifecycle":2,"lines":1,"link":2,"list":1,"live":1,"lives":1,"longer":1,"look":1,"loop":1,"loose":1,"main":2,"manifest":4,"markdown":1,"matching":3,"mcp":1,"md":2,"mdbook":1,"mid":2,"missing":1,"mode":1,"model":1,"more":1,"moves":1,"mutating":1,"mutations":1,"names":1,"narrow":1,"nearly":1,"needs":1,"no":3,"node":4,"noisy":1,"normally":1,"not":2,"observed":1,"of":2,"on":5,"once":2,"one":1,"ones":1,"only":3,"optional":1,"or":4,"order":1,"org":2,"output":4,"over":1,"overrides":1,"own":1,"page":6,"pages":3,"pandoc":1,"paragraph":2,"parsed":2,"part":1,"passes":1,"payload":1,"per":3,"pipeline":6,"plugin":1,"plugins":2,"post":2,"pre":1,"predicate":1,"prefix":1,"prelude":1,"preprocessors":1,"previewing":1,"previous":1,"processing":1,"protocol":1,"py3":1,"python":1,"rather":1,"ratio":1,"raw":1,"reason":1,"redaction":1,"reference":2,"regex":1,"related":1,"release":1,"remark":1,"remediations":1,"render":2,"renderer":1,"rendering":1,"resident":1,"rest":1,"result":1,"resulting":1,"reverts":1,"rewrites":2,"roll":1,"run":3,"running":1,"runs":1,"runtime":1,"s":4,"safe":1,"scaffold":1,"schema":3,"second":1,"security":3,"see":1,"seeded":1,"sees":3,"selectors":2,"serialised":1,"server":1,"shape":1,"shaped":1,"shell":1,"ship":1,"side":2,"sidecar":2,"size":1,"skeleton":1,"skip":1,"so":2,"spawn":1,"speak":1,"splblock":1,"src":2,"stage":5,"stages":2,"stay":1,"stderr":1,"stdin":1,"stdout":2,"strict":1,"string":2,"structure":1,"subprocess":1,"summary":1,"surfaces":1,"switch":1,"t":3,"table":1,"tells":1,"template":1,"than":1,"that":8,"the":48,"theme":5,"then":1,"these":1,"this":1,"three":2,"through":1,"time":2,"timeouts":1,"to":8,"toml":1,"touch":1,"transform":2,"transforms":1,"tree":2,"two":1,"type":2,"typed":3,"types":1,"typescript":1,"typical":1,"under":1,"unlike":1,"untrusted":1,"use":2,"v1":2,"var":1,"vars":1,"vault":4,"version":1,"via":1,"violation":1,"walk":1,"went":1,"what":1,"when":2,"where":1,"whether":1,"which":3,"wikilink":1,"with":7,"workflow":1,"writes":1,"wrote":1,"you":3,"your":3,"zetl":6}},{"dl":445,"n":"Lifecycle Hooks","s":"automation/lifecycle-hooks","secs":[{"h":"Lifecycle Hooks","l":6,"t":"Git-style scripts that run at defined points during a zetl operation. Drop an executable into `.zetl/hooks/` named after a lifecycle point, and zetl invokes it with structured JSON on stdin and a handful of `ZETL_*` environment variables. These are separate from the AST-level [[Render Pipeline Hooks]] — lifecycle hooks fire *around* the build, not inside it. Use them to publish an RSS feed after `zetl build`, notify a chat room on `on-save`, or run a schema validator on `post-check`."},{"h":"Lifecycle points","l":12,"t":"| Hook | Fires | Can abort? | |------|-------|------------| | `pre-build` | Before `zetl build` renders pages | Yes | | `post-build` | After `zetl build` completes | No (warning on non-zero) | | `post-index` | After `zetl index` completes | No | | `post-check` | After `zetl check` collects diagnostics | No | | `pre-serve` | Before `zetl serve` binds | Yes | | `on-save` | After a page is saved via `zetl serve` | No | | `on-agent` | When an agent API request arrives | No | | `on-access-request` | When a user requests page access (collab mode) | No | \"Can abort\" means a non-zero exit cancels the parent operation; every other hook just logs a warning and continues."},{"h":"What a hook receives","l":27,"t":"- **stdin** — a JSON object with vault metadata, the page list, the link graph, and hook-specific fields (e.g. `saved` for `on-save`). If [[Time Travel|history is enabled]], a `history` object is included. - **environment** — `ZETL_HOOK` (the lifecycle name), `ZETL_VAULT_ROOT`, `ZETL_THEME`, `ZETL_VERSION`, plus hook-specific vars: `ZETL_OUT_DIR` on build hooks, `ZETL_PORT` on serve hooks. - **working directory** — the vault root, regardless of where `zetl` was invoked from. - **timeout** — 30 seconds wall-clock. A hook that hangs is killed."},{"h":"Writing a hook","l":34,"t":"Create an executable file named exactly after the lifecycle point (no extension): Then mark it executable: That's it. The next `zetl build` will invoke it and your feed will land alongside the other built assets."},{"h":"Theme-bundled hooks","l":61,"t":"Themes can ship hooks in their own `hooks/` subdirectory. When you build with `--theme fountain`, zetl runs `.zetl/themes/fountain/hooks/post-build` **before** `.zetl/hooks/post-build`. Both run if both exist — the theme gets first crack, the vault overrides or augments. This lets a theme ship automation it genuinely needs (e.g. rewriting asset paths on publish) without stomping on vault-specific hooks you've added yourself."},{"h":"Listing and testing","l":75,"t":"Introspect the composed hook set for a vault/theme pair: Run a hook manually against real vault context — handy during development, or for wiring into CI: Extra JSON after `--` is merged into the context, so you can simulate hook-specific payloads without actually triggering the underlying event."},{"h":"Safe mode","l":93,"t":"`zetl build --safe-mode` and `zetl serve --safe-mode` disable every vault hook. Only theme hooks declared in the theme's `[[theme.hooks]]` manifest table run. That's the switch you want when previewing someone else's vault or auditing a theme."},{"h":"Related","l":97,"t":"- [[Render Pipeline Hooks]] — AST-level transforms that run *inside* the build, not around it. - [[Plugin Ecosystems]] — delegate transforms to Pandoc, mdBook, or remark. - [[Static Site Export]] — what `post-build` has to work with. - [[Watching for Changes]] — the built-in analogue of a `post-index` loop. - [[Configuration]] — `theme.toml`, `ZETL_*` environment variables, and the `.zetl/` directory layout."}],"tf":{"30":1,"a":19,"abort":2,"access":1,"actually":1,"added":1,"after":8,"against":1,"agent":1,"alongside":1,"an":4,"analogue":1,"and":8,"api":1,"are":1,"around":2,"arrives":1,"asset":1,"assets":1,"ast":2,"at":1,"auditing":1,"augments":1,"automation":1,"before":3,"binds":1,"both":2,"build":4,"built":2,"bundled":1,"can":4,"cancels":1,"changes":1,"chat":1,"ci":1,"clock":1,"collab":1,"collects":1,"completes":2,"composed":1,"configuration":1,"context":2,"continues":1,"crack":1,"create":1,"declared":1,"defined":1,"delegate":1,"development":1,"diagnostics":1,"directory":2,"disable":1,"drop":1,"during":2,"e":2,"ecosystems":1,"else":1,"enabled":1,"environment":3,"event":1,"every":2,"exactly":1,"executable":3,"exist":1,"exit":1,"export":1,"extension":1,"extra":1,"feed":2,"fields":1,"file":1,"fire":1,"fires":1,"first":1,"for":4,"from":2,"g":2,"genuinely":1,"gets":1,"git":1,"graph":1,"handful":1,"handy":1,"hangs":1,"has":1,"history":1,"hook":11,"hooks":10,"if":2,"in":3,"included":1,"inside":2,"into":3,"introspect":1,"invoke":1,"invoked":1,"invokes":1,"is":5,"it":7,"json":3,"just":1,"killed":1,"land":1,"layout":1,"lets":1,"level":2,"lifecycle":6,"link":1,"list":1,"listing":1,"logs":1,"loop":1,"manifest":1,"manually":1,"mark":1,"mdbook":1,"means":1,"merged":1,"metadata":1,"mode":2,"name":1,"named":2,"needs":1,"next":1,"no":7,"non":2,"not":2,"notify":1,"object":2,"of":3,"on":8,"only":1,"operation":2,"or":5,"other":2,"overrides":1,"own":1,"page":3,"pages":1,"pair":1,"pandoc":1,"parent":1,"paths":1,"payloads":1,"pipeline":2,"plugin":1,"plus":1,"point":2,"points":2,"previewing":1,"publish":2,"real":1,"receives":1,"regardless":1,"related":1,"remark":1,"render":2,"renders":1,"request":1,"requests":1,"rewriting":1,"room":1,"root":1,"rss":1,"run":6,"runs":1,"s":4,"safe":1,"saved":1,"schema":1,"scripts":1,"seconds":1,"separate":1,"serve":1,"set":1,"ship":2,"simulate":1,"site":1,"so":1,"someone":1,"specific":4,"static":1,"stdin":2,"stomping":1,"structured":1,"style":1,"subdirectory":1,"switch":1,"table":1,"testing":1,"that":5,"the":20,"their":1,"them":1,"theme":7,"themes":1,"then":1,"these":1,"this":1,"time":1,"timeout":1,"to":3,"transforms":2,"travel":1,"triggering":1,"underlying":1,"use":1,"user":1,"validator":1,"variables":2,"vars":1,"vault":8,"ve":1,"via":1,"wall":1,"want":1,"warning":2,"was":1,"watching":1,"what":2,"when":4,"where":1,"will":2,"wiring":1,"with":4,"without":2,"work":1,"working":1,"writing":1,"yes":2,"you":4,"your":1,"yourself":1,"zero":2,"zetl":3}},{"dl":485,"n":"Frontmatter Fields","s":"reference/frontmatter-fields","secs":[{"h":"Frontmatter Fields","l":6,"t":"Frontmatter is YAML at the very top of a page, fenced by `---` lines. zetl reads a handful of reserved keys — everything else passes through untouched for templates, hooks, and search. **Nothing is required.** A page with no frontmatter at all is valid."},{"h":"Reserved keys","l":23,"t":"zetl inspects these during indexing and rendering: | Key | Type | Meaning | | --- | --- | --- | | `title` | string | Display title. Falls back to the filename stem when absent. Does *not* rename the file or change its wikilink target. | | `parser` | string | Force a specific parser for this page. Values: `commonmark` (default) or `pandoc`. See [[Writing Pages]]. | | `tags` | list of strings | Surfaced in templates and in `zetl search --path`. Drives the tag cloud. | | `description` | string | Plain-text `<meta name=\"description\">` / social-preview text. Falls back to the first paragraph. | No key is mandatory. Omit any of them and zetl infers a sensible default."},{"h":"Parser selection","l":36,"t":"The `parser:` key wins over every other selector. Precedence (highest to lowest): 1. `parser:` in the page's frontmatter 2. First matching `[[parse.rule]]` glob in `.zetl/config.toml` 3. Top-level `[parse] default` in `.zetl/config.toml` 4. `commonmark` (zetl's built-in default) An unknown parser name (e.g. `parser: djot` when only `commonmark` is registered) produces a per-page error and the page is skipped — the rest of the build proceeds. Run `zetl ecosystem check` to see which parsers are available."},{"h":"Convention-only keys","l":47,"t":"The following are *conventions*, not reserved — zetl does not parse them, but templates and hooks consume them by name. Use whichever match your workflow. | Key | Convention | | --- | --- | | `date` | Publication / last-edited date. Any ISO-8601 string. Themes often sort by this. | | `status` | `draft`, `published`, `archived`. Hooks read it for publish-gating. | | `author` | Override for multi-author vaults. | | `aliases` | Alternate titles — surfaced in some themes; not currently resolved by the wikilink matcher. | Anything you invent is fair game — add `project_code: Q2-2026`, `mood: 🔥`, `reading_time: 8m`. Templates see it via `page.frontmatter.<key>`."},{"h":"Template variables","l":60,"t":"Every HTML render receives a `page` object. The fields writers care about: | Variable | Source | | --- | --- | | `page.title` | `title:` frontmatter, or the filename stem if absent. | | `page.slug` | URL-safe page path, relative to vault root. | | `page.frontmatter` | The entire parsed YAML as a JSON object. Access nested keys as `page.frontmatter.date`, `page.frontmatter.tags[0]`, etc. | | `page.description` | Either the frontmatter `description:` or the first paragraph. | | `page.content_html` | Rendered HTML body. | | `page.content_raw` | Original Markdown source. | | `page.backlinks` | List of inbound links. | | `page.outlinks` | List of outbound links (with `is_dead` flag). | | `page.breadcrumbs` | Directory path components. | | `page.history` | Snapshot timeline. `null` when history is unavailable. | `page.frontmatter` is the *raw* parsed document — any key you put in the YAML appears here verbatim. This is how templates pick up your custom fields without any template changes."},{"h":"Interaction with wikilinks","l":89,"t":"Frontmatter `title:` changes *display*, not *addressing*. A page at `notes/zettelkasten.md` is linked as `[[zettelkasten]]` or `[[Zettelkasten]]` regardless of whether its frontmatter declares `title: The Zettelkasten Method`. The rendered heading and the entry in every other page's backlinks will show the title-cased frontmatter value. If you want `[[The Zettelkasten Method]]` to resolve, rename the file."},{"h":"Interaction with search","l":95,"t":"`zetl search` reads both the body *and* the serialised frontmatter text. A query for `status: draft` matches draft pages. `tags:` values are searchable by their string."},{"h":"Interaction with SPL","l":99,"t":"Frontmatter keys are ground-truth facts for SPL reasoning under `--features reason`. A rule can test `@page.frontmatter.status == \"published\"` (subject to the SPL syntax — see [[Writing SPL]])."},{"h":"Related","l":103,"t":"- [[Writing Pages]] - [[Tags and Frontmatter]] - [[Organising Your Vault]] - [[CLI Overview]] - [[Configuration]]"}],"tf":{"1":1,"2":1,"3":1,"4":1,"8601":1,"a":11,"about":1,"absent":2,"access":1,"add":1,"addressing":1,"all":1,"alternate":1,"an":1,"and":9,"any":4,"anything":1,"appears":1,"are":4,"as":3,"at":3,"author":1,"available":1,"back":2,"backlinks":1,"body":2,"both":1,"build":1,"built":1,"but":1,"by":5,"can":1,"care":1,"cased":1,"change":1,"changes":2,"cli":1,"cloud":1,"components":1,"configuration":1,"consume":1,"convention":2,"conventions":1,"currently":1,"custom":1,"date":1,"declares":1,"default":3,"directory":1,"display":2,"document":1,"does":2,"draft":1,"drives":1,"during":1,"e":1,"edited":1,"either":1,"else":1,"entire":1,"entry":1,"error":1,"etc":1,"every":3,"everything":1,"facts":1,"fair":1,"falls":2,"fenced":1,"fields":3,"file":2,"filename":2,"first":3,"flag":1,"following":1,"for":6,"force":1,"frontmatter":12,"g":1,"game":1,"gating":1,"glob":1,"ground":1,"handful":1,"heading":1,"here":1,"highest":1,"history":1,"hooks":3,"how":1,"html":2,"if":2,"in":9,"inbound":1,"indexing":1,"infers":1,"inspects":1,"interaction":3,"invent":1,"is":11,"iso":1,"it":2,"its":2,"json":1,"key":5,"keys":5,"last":1,"level":1,"lines":1,"linked":1,"links":2,"list":3,"lowest":1,"mandatory":1,"markdown":1,"match":1,"matcher":1,"matches":1,"matching":1,"meaning":1,"multi":1,"name":2,"nested":1,"no":2,"not":5,"nothing":1,"object":2,"of":8,"often":1,"omit":1,"only":2,"or":5,"organising":1,"original":1,"other":2,"outbound":1,"over":1,"override":1,"overview":1,"page":9,"pages":3,"paragraph":2,"parse":1,"parsed":2,"parser":3,"parsers":1,"passes":1,"path":2,"per":1,"pick":1,"plain":1,"precedence":1,"preview":1,"proceeds":1,"produces":1,"publication":1,"publish":1,"put":1,"query":1,"raw":1,"read":1,"reads":2,"reasoning":1,"receives":1,"regardless":1,"registered":1,"related":1,"relative":1,"rename":2,"render":1,"rendered":2,"rendering":1,"required":1,"reserved":3,"resolve":1,"resolved":1,"rest":1,"root":1,"rule":1,"run":1,"s":3,"safe":1,"search":2,"searchable":1,"see":4,"selection":1,"selector":1,"sensible":1,"serialised":1,"show":1,"skipped":1,"snapshot":1,"social":1,"some":1,"sort":1,"source":2,"specific":1,"spl":4,"stem":2,"string":5,"strings":1,"subject":1,"surfaced":2,"syntax":1,"tag":1,"tags":1,"target":1,"template":2,"templates":5,"test":1,"text":3,"the":26,"their":1,"them":3,"themes":2,"these":1,"this":3,"through":1,"timeline":1,"title":2,"titles":1,"to":7,"top":2,"truth":1,"type":1,"unavailable":1,"under":1,"unknown":1,"untouched":1,"up":1,"url":1,"use":1,"valid":1,"value":1,"values":2,"variable":1,"variables":1,"vault":2,"vaults":1,"verbatim":1,"very":1,"via":1,"want":1,"when":3,"whether":1,"which":1,"whichever":1,"wikilink":2,"wikilinks":1,"will":1,"wins":1,"with":5,"without":1,"workflow":1,"writers":1,"writing":3,"yaml":3,"you":3,"your":3,"zetl":5}},{"dl":1153,"n":"CLI Overview","s":"reference/cli-overview","secs":[{"h":"CLI Overview","l":6,"t":"Every `zetl` subcommand at a glance, with the flags writers actually use. This page is the reference — the linked how-to pages give the narrative. Run `zetl <cmd> --help` for the authoritative flag list for any command. This guide tracks zetl **v0.5.0**."},{"h":"Global flags","l":12,"t":"Every subcommand honours these, wherever they make sense. | Flag | Env var | Purpose | | --- | --- | --- | | `-d, --dir <DIR>` | `ZETL_DIR` | Vault root. Default: `.` (current directory). | | `-f, --format <FORMAT>` | `ZETL_FORMAT` | `json` / `table` / `auto`. Auto picks table for a TTY, JSON for a pipe. | | `--json` | — | Shorthand for `-f json`. | | `--no-cache` | `ZETL_NO_CACHE` | Ignore the cached index; force a full rescan. | | `--no-color` | `NO_COLOR` | Disable ANSI colour. | | `-q, --quiet` | — | Suppress non-essential output. | | `-v, --verbose` | — | Increase verbosity. Repeatable (`-vv`). | | `--no-input` | — | Disable interactive prompts; fail if input is needed. | | `--at <TIME-EXPR>` | — | Query the vault at a historical point. Requires `--features history`. See [[Time Travel]]. | | `-V, --version` | — | Print zetl version. |"},{"h":"Subcommand summary","l":29,"t":"| Command | Description | Feature flag | | --- | --- | --- | | [`index`](#zetl-index) | Build or refresh the link index | — | | [`list`](#zetl-list) | List every page in the vault | — | | [`stats`](#zetl-stats) | Summary counts and most-linked pages | — | | [`links`](#zetl-links) | Forward links from a page | — | | [`backlinks`](#zetl-backlinks) | Backlinks to a page | — | | [`check`](#zetl-check) | Dead links, orphans, syntax, SPL diagnostics | — | | [`search`](#zetl-search) | Full-text search (BM25, optional semantic) | `semantic` for `--semantic`/`--hybrid` | | [`similar`](#zetl-similar) | SimHash title matches | — | | [`path`](#zetl-path) | Shortest link path between two pages | — | | [`export`](#zetl-export) | Dump the full link graph | — | | [`blocks`](#zetl-blocks) | List or resolve Merkle blocks | — | | [`view`](#zetl-view) | Two-pane terminal reader | — | | [`serve`](#zetl-serve) | Local web server | — | | [`build`](#zetl-build) | Static-site HTML export | — | | [`watch`](#zetl-watch) | Stream vault changes as NDJSON | — | | [`diff`](#zetl-diff) | Graph diff against a git ref or snapshot | — | | [`theme`](#zetl-theme) | List / install / remove / export themes | — | | [`hook`](#zetl-hook) | Author, test, and inspect hooks | — | | [`ecosystem`](#zetl-ecosystem) | Probe Pandoc / mdBook / remark runtimes | `ecosystems-v1` | | [`ast`](#zetl-ast) | Inspect or diff zetl-ext AST | — | | [`agent`](#zetl-agent) | Run an agent-lifecycle hook | — | | [`invite`](#zetl-invite) | Multi-user invitation token | collab (built-in) | | [`agent-token`](#zetl-agent-token) | Derive a headless API token | collab | | [`derive-ssh-key`](#zetl-derive-ssh-key) | Derive an SSH ed25519 key from a mnemonic | collab | | [`cap`](#zetl-cap) | Capability-URL static sharing operations | — | | [`reason`](#zetl-reason) | SPL queries over the vault | `reason` | | [`mcp`](#zetl-mcp) | Start an MCP server | `mcp` | | [`delegate`](#zetl-delegate) | Issue a delegate JWT | `mcp` | | [`completions`](#zetl-completions) | Emit a shell completion script | — | | [`man`](#zetl-man) | Emit a roff(7) man page | — | ---"},{"h":"zetl index","l":66,"t":"Scan the vault and write the cached link index to `.zetl/index.json`. Incremental by default; `--no-cache` forces a full rescan. Key flags: `--exclude <PATTERN>` (repeatable gitignore syntax), `--include-hidden` (walk `.claude/`, `.obsidian/`, etc.)."},{"h":"zetl list","l":77,"t":"Enumerate every page zetl can see. Useful in pipes."},{"h":"zetl stats","l":85,"t":"Counts: files, links, orphans, dead links, plus the most-linked pages. `--top <N>` sets how many leaders to print (default 10)."},{"h":"zetl links","l":89,"t":"Forward links from a page. See [[Following Links]]. Key flags: `--depth <N>` (traverse N hops), `--context <N>` (include N characters of surrounding text), `--fuzzy` (case-insensitive partial page-name match), `--with-conclusions` (SPL conclusions each linked page contributes; requires `reason`)."},{"h":"zetl backlinks","l":100,"t":"Same flags as `zetl links`, but inbound. See [[Backlinks]]."},{"h":"zetl check","l":108,"t":"Validate vault health: dead links, orphans, syntax errors, SPL diagnostics, and SPL drift. See [[Finding Orphans and Dead Links]]. Key flags: `--dead-links`, `--orphans`, `--syntax`, `--spl`, `--drift` (show only that category), `--fail-on error|warning` (CI gate), `--theme <NAME>` (for hook-discovery)."},{"h":"zetl search","l":119,"t":"Full-text search across vault content. See [[Searching]]. Key flags: `--limit <N>` (default 50), `--context <N>` (default 40), `--case-sensitive`, `--path <GLOB>` (restrict to matching files), `--near <PAGE>` with `--depth <N>` (restrict to pages within N link-hops of PAGE), `--semantic` and `--hybrid` (require `--features semantic`)."},{"h":"zetl similar","l":131,"t":"SimHash locality-sensitive title match. See [[Similar Pages]]. Key flags: `--threshold <N>` (max Hamming distance, default 12), `--limit <N>` (default 10)."},{"h":"zetl path","l":137,"t":"Shortest link path between two pages. See [[Following Links]]. Key flag: `--max-depth <N>` (default 10)."},{"h":"zetl export","l":147,"t":"Dump the full link graph — pages + edges — to JSON. Pipe into Gephi, Obsidian Canvas, or your own tooling."},{"h":"zetl blocks","l":151,"t":"List the [[Blocks|Merkle blocks]] of a page, or resolve a block by its BLAKE3 hash. Key flags: `--type heading|paragraph|spl|code|table|list|blockquote|frontmatter|all`, `--resolve <HASH>` (full or prefix; mutually exclusive with PAGE)."},{"h":"zetl view","l":162,"t":"Xanadu-style two-pane terminal reader. See [[Terminal Viewer]]. Key flags: `--main-width <pct>` (30–80, default 58), `--context-lines <N>` (1–20, default 5). Run without a page name to open a picker."},{"h":"zetl serve","l":168,"t":"Local web server. See [[Web Server]]. Key flags: `--port <PORT>` (default 3000), `--theme <NAME>`, `--public <DIR>` (files that override generated pages), `--collab` (multi-user), `--init-owner` / `--owner-name <NAME>` (first-time collab bootstrap), `--hostname <HOST>` (WebAuthn relying-party; env: `ZETL_HOSTNAME`), `--server-key-seed <MNEMONIC>` (deterministic keys; env: `ZETL_SERVER_KEY_SEED`), `--git-poll-interval 30s`, `--safe-mode` (only theme-declared hooks run)."},{"h":"zetl build","l":180,"t":"Generate a static HTML site. See [[Static Site Export]]. Key flags: `-o, --out-dir <DIR>` (default `dist`), `--theme <NAME>`, `--public <DIR>`, `--site-url <URL>` (absolute og:image URLs), `--safe-mode`, `--strict-parsers` (promote mixed-parser warnings to errors)."},{"h":"zetl watch","l":191,"t":"Watch the vault for changes and emit NDJSON graph events. See [[Watching for Changes]]. Key flags: `--debounce <MS>` (default 150, min 10, max 5000), `--exec <CMD>` (shell command run once per event with the event JSON on stdin), `--exclude`, `--include-hidden`."},{"h":"zetl diff","l":201,"t":"Graph-level diff against a git ref or jj change-ID. Key flags: `--from <REF>` or `--since <DATE>`, `--filter pages|links|orphans|dead-links`."},{"h":"zetl theme","l":212,"t":"Manage themes. See [[Customising the Look]]. Subcommands: | Subcommand | Purpose | | --- | --- | | `zetl theme list` | List bundled + installed themes | | `zetl theme install <SOURCE>` | Install from git (`user/repo`, URL, `git@…#ref`); flags: `--path`, `--name`, `--force` | | `zetl theme remove <NAME>` | Remove an installed theme | | `zetl theme export <NAME>` | Copy a bundled theme into `.zetl/themes/` for customisation; `--force` |"},{"h":"zetl hook","l":225,"t":"Author, test, and inspect hooks. See [[Lifecycle Hooks]] and [[Render Pipeline Hooks]]. | Subcommand | Purpose | | --- | --- | | `zetl hook list` | Active hooks for this vault + theme | | `zetl hook run <NAME> [-- <EXTRA>...]` | Run a lifecycle hook with real vault context | | `zetl hook new <STAGE> <NAME>` | Scaffold a render-pipeline hook; flags: `--lang py\\|js\\|sh`, `--ecosystem pandoc\\|mdbook\\|remark`, `--force` | | `zetl hook test <NAME>` | Run a hook against its fixture and diff the golden; `--update` regenerates | | `zetl hook fixture --from <PAGE> --hook <NAME>` | Capture a vault page into the hook's fixture dir | | `zetl hook watch <NAME>` | Live-reload the hook's persistent-mode subprocess on source change | | `zetl hook coverage` | Per-hook coverage from the most-recent build; `--stage`, `--json` | | `zetl hook dry-run <STAGE>/<NAME>` | Print pages the hook's selector matches; hook is not invoked; `--limit`, `--theme` | | `zetl hook capabilities` | Probe every hook's supported stages + AST types; `--stage`, `--json` |"},{"h":"zetl ecosystem","l":241,"t":"Probe plugin ecosystems. Requires `ecosystems-v1` feature flag. - `zetl ecosystem check` — per-ecosystem detection, version, configured-hook count, reachable plugins. Exits 0 when all *configured* ecosystems are available. See [[Plugin Ecosystems]]."},{"h":"zetl ast","l":247,"t":"Inspect the zetl-ext AST for a page or diff two AST JSON documents. - `zetl ast sample <FILE>` — print canonical AST JSON. `--stage pre-parse|transform|post-render` chooses which stage's input to emit (default `transform`). - `zetl ast diff <BEFORE> <AFTER>` — tree-aware structural diff; non-zero exit on non-empty diff."},{"h":"zetl agent","l":254,"t":"Agent lifecycle integration for LLM / automation hooks. - `zetl agent run <NAME> [-- <EXTRA>...]` — run an on-agent hook with task context. Flags: `--pages <TARGET>...`, `--budget <TOKENS>` (0 = unlimited), `--theme`."},{"h":"zetl invite","l":260,"t":"Generate an invitation token for a collaborator. See [[Invitations]]. Key flags: `--as <USER>` (inviter), `--role reader|editor|admin`, `--pages <GLOB>` (scoped grant), `--expires 72h|24h|7d` (default 72h), `--host`, `--port` (URL generation)."},{"h":"zetl agent-token","l":271,"t":"Derive a headless API token from a BIP39 mnemonic."},{"h":"zetl derive-ssh-key","l":279,"t":"Derive an SSH ed25519 key from a BIP39 mnemonic. Useful for ephemeral containers where one seed covers the server key and the git SSH key."},{"h":"zetl cap","l":287,"t":"Capability-URL static-site operations. See [[Capability URLs]]. | Subcommand | Purpose | | --- | --- | | `zetl cap genkey` | Emit the content-encryption secret + signing keypair (once) | | `zetl cap invite <NAME> --cohort <ID>` | Issue an invitation grant; flags: `--expires`, `--pages <GLOB>`, `--recipient <PUBKEY>`, `--via enrol-page`, `--split-key`, `--site-url`, `--slug` | | `zetl cap list` | List issued grants; `--cohort`, `--output` | | `zetl cap revoke <GRANT_ID>` | Revoke a grant | | `zetl cap rotate --cohort <ID>` | Rotate a cohort's content-key salt (URLs stay stable) | | `zetl cap finalise <GRANT_ID>` | Mark a grant operator-confirmed; `--rotate-grant` | | `zetl cap check` | Stale-grant + public-safety audit; `--public-safety` | | `zetl cap sweep` | Mark past-expiry grants as revoked | | `zetl cap pair` | SPAKE2 pubkey handoff; `--grantor` / `--grantee`, `--peer`, `--phrase`, `--pubkey` | | `zetl cap audit-diff [OLD] [NEW]` | Scan a diff for malicious content; `--corpus`, `--corpus-root` | | `zetl cap rotate-signing-key` | Rotate the Ed25519 vault-signing key (rebuilds every page) | | `zetl cap emergency-shutdown` | Print the takedown checklist (no files changed) |"},{"h":"zetl reason","l":306,"t":"SPL queries over the vault. Requires `--features reason`. See [[Running Queries]]. Subcommands (consult `zetl reason --help` inside a feature-reason build): `status`, `explain`, `conflicts`, `why-not`, `what-if`."},{"h":"zetl mcp","l":312,"t":"Start a Model Context Protocol server. Requires `--features mcp`. See [[MCP Server]]."},{"h":"zetl delegate","l":316,"t":"Issue a delegate JWT for an MCP client. Requires `--features mcp`."},{"h":"zetl completions","l":320,"t":"Print a shell-completion script. Supported shells: `bash`, `zsh`, `fish`, `elvish`, `powershell`."},{"h":"zetl man","l":330,"t":"Print a roff(7) man page. Preview with `zetl man | man -l -`."},{"h":"Related","l":334,"t":"- [[Configuration]] - [[Installation]] - [[Quick Start]] - [[Glossary]] - [[Frontmatter Fields]]"}],"tf":{"0":3,"1":1,"10":4,"12":1,"150":1,"20":1,"30":1,"3000":1,"40":1,"5":2,"50":1,"5000":1,"58":1,"7":2,"72h":1,"80":1,"a":40,"absolute":1,"across":1,"active":1,"actually":1,"against":3,"agent":7,"all":1,"an":9,"and":11,"ansi":1,"any":1,"api":2,"are":1,"as":3,"ast":7,"at":2,"audit":1,"author":2,"authoritative":1,"auto":1,"automation":1,"available":1,"aware":1,"backlinks":4,"between":2,"bip39":2,"blake3":1,"block":1,"blocks":5,"bm25":1,"bootstrap":1,"build":5,"built":1,"bundled":2,"but":1,"by":2,"cached":2,"can":1,"canonical":1,"canvas":1,"cap":2,"capability":3,"capture":1,"case":1,"category":1,"change":2,"changed":1,"changes":3,"characters":1,"check":2,"checklist":1,"chooses":1,"ci":1,"cli":1,"client":1,"cohort":1,"collab":4,"collaborator":1,"colour":1,"command":3,"completion":2,"completions":2,"conclusions":1,"configuration":1,"configured":2,"confirmed":1,"consult":1,"containers":1,"content":4,"context":3,"contributes":1,"copy":1,"count":1,"counts":2,"coverage":1,"covers":1,"current":1,"customisation":1,"customising":1,"dead":4,"declared":1,"default":15,"delegate":4,"derive":6,"description":1,"detection":1,"deterministic":1,"diagnostics":2,"diff":10,"dir":1,"directory":1,"disable":2,"discovery":1,"distance":1,"documents":1,"drift":1,"dump":2,"each":1,"ecosystem":3,"ecosystems":3,"ed25519":3,"edges":1,"emit":5,"empty":1,"encryption":1,"enumerate":1,"env":3,"ephemeral":1,"errors":2,"essential":1,"etc":1,"event":2,"events":1,"every":6,"exclusive":1,"exit":1,"exits":1,"expiry":1,"export":5,"ext":2,"fail":1,"feature":3,"fields":1,"files":4,"finding":1,"first":1,"fixture":2,"flag":5,"flags":19,"following":2,"for":17,"force":1,"forces":1,"forward":2,"from":7,"frontmatter":1,"full":7,"gate":1,"generate":2,"generated":1,"generation":1,"gephi":1,"git":4,"gitignore":1,"give":1,"glance":1,"global":1,"glossary":1,"golden":1,"grant":5,"grants":2,"graph":5,"guide":1,"hamming":1,"handoff":1,"hash":1,"headless":2,"health":1,"historical":1,"honours":1,"hook":15,"hooks":7,"hops":2,"how":2,"html":2,"id":1,"if":1,"ignore":1,"image":1,"in":3,"inbound":1,"include":1,"increase":1,"incremental":1,"index":5,"input":2,"insensitive":1,"inside":1,"inspect":4,"install":2,"installation":1,"installed":2,"integration":1,"interactive":1,"into":3,"invitation":3,"invitations":1,"invite":2,"inviter":1,"invoked":1,"is":3,"issue":3,"issued":1,"its":2,"jj":1,"json":5,"jwt":2,"key":21,"keypair":1,"keys":1,"leaders":1,"level":1,"lifecycle":4,"link":7,"linked":4,"links":11,"list":9,"live":1,"llm":1,"local":2,"locality":1,"look":1,"make":1,"malicious":1,"man":4,"manage":1,"many":1,"mark":2,"match":2,"matches":2,"matching":1,"max":2,"mcp":5,"mdbook":1,"merkle":2,"min":1,"mixed":1,"mnemonic":3,"mode":1,"model":1,"most":3,"multi":2,"mutually":1,"n":3,"name":2,"narrative":1,"ndjson":2,"needed":1,"no":1,"non":3,"not":1,"obsidian":1,"of":3,"og":1,"on":4,"once":2,"one":1,"only":2,"open":1,"operations":2,"operator":1,"optional":1,"or":10,"orphans":4,"output":1,"over":2,"override":1,"overview":1,"own":1,"page":17,"pages":10,"pandoc":1,"pane":2,"parser":1,"partial":1,"party":1,"past":1,"path":4,"per":3,"persistent":1,"picker":1,"picks":1,"pipe":2,"pipeline":2,"pipes":1,"plugin":2,"plugins":1,"plus":1,"point":1,"prefix":1,"preview":1,"print":7,"probe":3,"promote":1,"prompts":1,"protocol":1,"pubkey":1,"public":1,"purpose":4,"queries":3,"query":1,"quick":1,"reachable":1,"reader":2,"real":1,"reason":3,"rebuilds":1,"recent":1,"ref":2,"reference":1,"refresh":1,"regenerates":1,"related":1,"reload":1,"relying":1,"remark":1,"remove":2,"render":2,"repeatable":2,"require":1,"requires":6,"rescan":2,"resolve":2,"restrict":2,"revoke":1,"revoked":1,"roff":2,"root":1,"rotate":2,"run":8,"running":1,"runtimes":1,"s":6,"safety":1,"salt":1,"same":1,"scaffold":1,"scan":2,"scoped":1,"script":2,"search":4,"searching":1,"secret":1,"see":19,"seed":1,"selector":1,"semantic":1,"sense":1,"sensitive":1,"serve":2,"server":7,"sets":1,"sharing":1,"shell":3,"shells":1,"shortest":2,"shorthand":1,"show":1,"signing":2,"simhash":2,"similar":3,"site":4,"snapshot":1,"source":1,"spake2":1,"spl":6,"ssh":5,"stable":1,"stage":1,"stages":1,"stale":1,"start":3,"static":5,"stats":2,"stay":1,"stdin":1,"stream":1,"structural":1,"style":1,"subcommand":6,"subcommands":2,"subprocess":1,"summary":2,"supported":2,"suppress":1,"surrounding":1,"syntax":3,"table":1,"takedown":1,"task":1,"terminal":3,"test":2,"text":3,"that":2,"the":31,"theme":6,"themes":3,"these":1,"they":1,"this":3,"time":2,"title":2,"to":10,"token":6,"tooling":1,"tracks":1,"travel":1,"traverse":1,"tree":1,"tty":1,"two":5,"types":1,"unlimited":1,"url":4,"urls":3,"use":1,"useful":2,"user":2,"v0":1,"validate":1,"var":1,"vault":14,"verbosity":1,"version":2,"view":2,"viewer":1,"walk":1,"warnings":1,"watch":3,"watching":1,"web":3,"webauthn":1,"when":1,"where":1,"wherever":1,"which":1,"with":7,"within":1,"without":1,"write":1,"writers":1,"xanadu":1,"your":1,"zero":1,"zetl":65}},{"dl":722,"n":"Glossary","s":"reference/glossary","secs":[{"h":"Glossary","l":6,"t":"Every term used across the zetl guide, in one alphabetised list. Entries link to the primary page where the concept is explained in depth."},{"h":"A","l":10,"t":"**AST** — Abstract syntax tree. zetl uses a canonical schema (*zetl-ext AST v1*) that every render-pipeline stage receives and returns. Inspect with `zetl ast sample`. See [[Render Pipeline Hooks]]."},{"h":"B","l":14,"t":"**Backlink** — An inbound wikilink. If page *A* contains `[[B]]`, then *B* has a backlink from *A*. Surface with `zetl backlinks`. See [[Backlinks]]. **BLAKE3** — The cryptographic hash zetl uses for content-addressable blocks and the Merkle leaves that feed the incremental cache. Fast, parallelisable, keyed. See [[Blocks]]. **Block** — A content-addressable unit within a page: a heading, paragraph, code fence, table, blockquote, list, SPL block, or frontmatter. Each block has a BLAKE3 hash. See [[Blocks]]."},{"h":"C","l":22,"t":"**Capability URL** — A sharing link with an embedded fragment secret (`#k=…`) that decrypts a static-built vault client-side. No server required. See [[Capability URLs]]. **CRDT** — Conflict-free replicated data type. zetl uses Peritext for real-time co-editing so two authors can type into the same paragraph without conflicts or last-writer-wins. See [[Co-editing]]."},{"h":"D","l":28,"t":"**Dead link** — A wikilink that points at a nonexistent page. `zetl check --dead-links` lists them. See [[Finding Orphans and Dead Links]]. **Defeasible logic** — A logic where conclusions can be revoked when stronger evidence arrives: \"birds fly; penguins don't; Tweety is a penguin\" correctly concludes Tweety doesn't fly. zetl's optional reasoning layer is built on this. See [[What is Defeasible Reasoning]]."},{"h":"E","l":34,"t":"**Ecosystem** — An external render toolchain zetl can plug into: Pandoc, mdBook, or remark. Each is a set of render-pipeline adapters gated behind a cargo feature flag (`ecosystem-pandoc`, `ecosystem-mdbook`, `ecosystem-remark`). See [[Plugin Ecosystems]]."},{"h":"F","l":38,"t":"**Forward link** — An outbound wikilink. If page *A* contains `[[B]]`, *A* has a forward link to *B*. Surface with `zetl links`. See [[Following Links]]. **Frontmatter** — YAML metadata fenced between `---` lines at the top of a page. zetl reads a handful of reserved keys (`title`, `tags`, `parser`, `description`) and passes the rest through to templates. See [[Frontmatter Fields]]."},{"h":"H","l":44,"t":"**Hook** — An executable zetl invokes at a lifecycle point (`pre-build`, `post-build`, `on-save`) or inside the render pipeline (`pre-parse`, `transform`, `post-render`). Hooks live under `.zetl/hooks/` in the vault or `hooks/` in a theme. See [[Lifecycle Hooks]], [[Render Pipeline Hooks]]."},{"h":"J","l":48,"t":"**jj (Jujutsu)** — The VCS engine that backs zetl's silent snapshots when `--features history` is enabled. Commits happen in `.zetl/jj/` without touching your git repo. See [[Snapshots Under the Hood]]."},{"h":"M","l":52,"t":"**MCP** — Model Context Protocol. A standard for exposing structured tool access to LLM agents. `zetl mcp` serves the vault as an MCP endpoint. See [[MCP Server]]. **Merkle tree** — A hash tree over vault blocks. Changing one paragraph only invalidates the hashes along its root-path, so zetl's cache reparses just that page and its consumers. See [[Blocks]]."},{"h":"O","l":58,"t":"**Orphan** — A page with no inbound *and* no outbound wikilinks. Either write one, link to it, or delete it. See [[Finding Orphans and Dead Links]]."},{"h":"P","l":62,"t":"**Passkey** — A WebAuthn credential — Touch ID, Windows Hello, a hardware key like a YubiKey. zetl's multi-user mode uses passkeys so there are no passwords in the vault. See [[Passkeys and Accounts]]."},{"h":"S","l":66,"t":"**SimHash** — A locality-sensitive hash that produces similar bit-strings for similar inputs. `zetl similar` uses it to find title matches within a Hamming-distance threshold. See [[Similar Pages]]. **Slug** — The URL-safe version of a page title, used in paths on the built site. `Zettelkasten Method.md` becomes the slug `zettelkasten-method`. **SPA shell** — Single-page-application navigation shell used by the web server and built site. Same-origin clicks swap only the `<main data-zetl-volatile>` region, keeping the sidebar and graph widget mounted. Opt in via `[spa] enabled = true` in `theme.toml`. See [[Customising the Look]]. **SPL (Spindle Lisp)** — The defeasible-logic language zetl uses for vault rules under `--features reason`. Rules live in fenced code blocks (```` ```spl ````) inside your pages; the reasoner consumes them globally. See [[Writing SPL]]."},{"h":"T","l":76,"t":"**Transclusion** — Embedding part of one page inside another. The syntax is `![[Page]]` to embed a whole page, `![[Page#Heading]]` for a section, or `![[Page^block-id]]` for a specific block. See [[Embeds and Transclusion]]."},{"h":"V","l":80,"t":"**Vault** — Any directory zetl operates on. No registration, no config file required. A vault becomes *zetl-aware* the first time `zetl index` runs against it and the `.zetl/` cache directory appears. See [[Vaults]]."},{"h":"W","l":84,"t":"**WebAuthn** — The W3C standard for passkey authentication. zetl's multi-user mode is a WebAuthn relying-party. See [[Passkeys and Accounts]]. **Wikilink** — A `[[Page Name]]` reference. The foundational link syntax zetl is built around. Variants: `[[Page|Alias]]`, `[[Page#Heading]]`, `[[Page^block-id]]`, `![[Page]]` (transclusion). See [[Wikilinks]]."},{"h":"Related","l":90,"t":"- [[Index|zetl User Guide]] - [[CLI Overview]] - [[Configuration]] - [[Frontmatter Fields]]"}],"tf":{"a":39,"abstract":1,"access":1,"accounts":2,"across":1,"adapters":1,"addressable":2,"against":1,"agents":1,"along":1,"alphabetised":1,"an":6,"and":13,"another":1,"any":1,"appears":1,"application":1,"are":1,"around":1,"arrives":1,"as":1,"ast":2,"at":3,"authentication":1,"authors":1,"aware":1,"b":3,"backlink":2,"backlinks":1,"backs":1,"be":1,"becomes":2,"behind":1,"between":1,"birds":1,"bit":1,"blake3":2,"block":4,"blockquote":1,"blocks":6,"built":5,"by":1,"c":1,"cache":3,"can":3,"canonical":1,"capability":2,"cargo":1,"changing":1,"cli":1,"clicks":1,"client":1,"co":2,"code":2,"commits":1,"concept":1,"concludes":1,"conclusions":1,"config":1,"configuration":1,"conflict":1,"conflicts":1,"consumers":1,"consumes":1,"contains":2,"content":2,"context":1,"correctly":1,"crdt":1,"credential":1,"cryptographic":1,"customising":1,"d":1,"data":1,"dead":3,"decrypts":1,"defeasible":3,"delete":1,"depth":1,"directory":2,"distance":1,"doesn":1,"don":1,"e":1,"each":2,"ecosystem":1,"ecosystems":1,"editing":2,"either":1,"embed":1,"embedded":1,"embedding":1,"embeds":1,"enabled":1,"endpoint":1,"engine":1,"entries":1,"every":2,"evidence":1,"executable":1,"explained":1,"exposing":1,"ext":1,"external":1,"f":1,"fast":1,"feature":1,"feed":1,"fence":1,"fenced":2,"fields":2,"file":1,"find":1,"finding":2,"first":1,"flag":1,"fly":2,"following":1,"for":8,"forward":2,"foundational":1,"fragment":1,"free":1,"from":1,"frontmatter":4,"gated":1,"git":1,"globally":1,"glossary":1,"graph":1,"guide":2,"h":1,"hamming":1,"handful":1,"happen":1,"hardware":1,"has":3,"hash":4,"hashes":1,"heading":1,"hello":1,"hood":1,"hook":1,"hooks":4,"id":1,"if":2,"in":10,"inbound":2,"incremental":1,"index":1,"inputs":1,"inside":3,"inspect":1,"into":2,"invalidates":1,"invokes":1,"is":9,"it":4,"its":2,"j":1,"jj":1,"jujutsu":1,"just":1,"keeping":1,"key":1,"keyed":1,"keys":1,"language":1,"last":1,"layer":1,"leaves":1,"lifecycle":2,"like":1,"lines":1,"link":7,"links":3,"lisp":1,"list":2,"lists":1,"live":2,"llm":1,"locality":1,"logic":3,"look":1,"m":1,"matches":1,"mcp":3,"mdbook":1,"merkle":2,"metadata":1,"mode":2,"model":1,"mounted":1,"multi":2,"navigation":1,"no":6,"nonexistent":1,"o":1,"of":5,"on":3,"one":4,"only":2,"operates":1,"opt":1,"optional":1,"or":7,"origin":1,"orphan":1,"orphans":2,"outbound":2,"over":1,"overview":1,"p":1,"page":12,"pages":2,"pandoc":1,"paragraph":3,"parallelisable":1,"part":1,"party":1,"passes":1,"passkey":2,"passkeys":3,"passwords":1,"path":1,"paths":1,"penguin":1,"penguins":1,"peritext":1,"pipeline":5,"plug":1,"plugin":1,"point":1,"points":1,"primary":1,"produces":1,"protocol":1,"reads":1,"real":1,"reasoner":1,"reasoning":2,"receives":1,"reference":1,"region":1,"registration":1,"related":1,"relying":1,"remark":1,"render":6,"reparses":1,"replicated":1,"repo":1,"required":2,"reserved":1,"rest":1,"returns":1,"revoked":1,"root":1,"rules":2,"runs":1,"s":6,"safe":1,"same":2,"schema":1,"secret":1,"section":1,"see":24,"sensitive":1,"server":3,"serves":1,"set":1,"sharing":1,"shell":2,"side":1,"sidebar":1,"silent":1,"simhash":1,"similar":3,"single":1,"site":2,"slug":2,"snapshots":2,"so":3,"spa":1,"specific":1,"spindle":1,"spl":3,"stage":1,"standard":2,"static":1,"strings":1,"stronger":1,"structured":1,"surface":2,"swap":1,"syntax":3,"t":3,"table":1,"templates":1,"term":1,"that":7,"the":30,"them":2,"theme":1,"then":1,"there":1,"this":1,"threshold":1,"through":1,"time":2,"title":2,"to":7,"tool":1,"toolchain":1,"top":1,"touch":1,"touching":1,"transclusion":3,"tree":3,"tweety":2,"two":1,"type":2,"under":3,"unit":1,"url":2,"urls":1,"used":3,"user":3,"uses":6,"v":1,"v1":1,"variants":1,"vault":8,"vaults":1,"vcs":1,"version":1,"via":1,"w":1,"w3c":1,"web":1,"webauthn":3,"what":1,"when":2,"where":2,"whole":1,"widget":1,"wikilink":4,"wikilinks":2,"windows":1,"wins":1,"with":5,"within":2,"without":2,"write":1,"writer":1,"writing":1,"yaml":1,"your":2,"yubikey":1,"zetl":18}},{"dl":673,"n":"Configuration","s":"reference/configuration","secs":[{"h":"Configuration","l":6,"t":"Every knob zetl exposes: environment variables, ignore files, `theme.toml`, and the `.zetl/` state directory. Nothing here is required — zetl runs on a bare directory of Markdown with zero configuration. Read this page when you want to change defaults."},{"h":"Environment variables","l":10,"t":"zetl-specific variables are prefixed `ZETL_`. The binary also honours `NO_COLOR` (a de-facto standard)."},{"h":"Read by the CLI","l":14,"t":"| Variable | Equivalent flag | Purpose | | --- | --- | --- | | `ZETL_DIR` | `-d, --dir` | Default vault root. | | `ZETL_FORMAT` | `-f, --format` | `json`, `table`, or `auto`. | | `ZETL_NO_CACHE` | `--no-cache` | Force full rescan. | | `NO_COLOR` | `--no-color` | Disable ANSI colour. | | `ZETL_HOSTNAME` | `--hostname` (on `serve --collab`) | WebAuthn relying-party hostname. | | `ZETL_SERVER_KEY_SEED` | `--server-key-seed` (on `serve --collab`, `derive-ssh-key`) | BIP39 mnemonic for deterministic key derivation in ephemeral deployments. | | `ZETL_CAP_SITE_URL` | `--site-url` (on `cap invite`) | Canonical site URL for capability-URL rendering. | | `ZETL_CAP_PAIR_PHRASE` | `--phrase` (on `cap pair`) | Pinned SPAKE2 phrase; test-harness hook. | Pass any of these once in your shell profile and skip the corresponding flag forever."},{"h":"Exported to hooks","l":29,"t":"Every hook zetl launches inherits these: | Variable | Value | | --- | --- | | `ZETL_HOOK` | Hook name (e.g. `post-build`, `callouts`). | | `ZETL_VAULT_ROOT` | Absolute path to the vault. | | `ZETL_THEME` | Active theme name. Empty string if no theme. | | `ZETL_VERSION` | zetl binary version (e.g. `0.5.0`). | | `ZETL_AST_SCHEMA_VERSION` | AST schema version (SemVer, e.g. `1.0.0`). Independent of `ZETL_VERSION`. | | `ZETL_MODE` | `build` or `serve`. | | `ZETL_OUT_DIR` | Build output directory. Empty string under `serve`. | | `ZETL_VERBOSE` | `true` / `false`. | | `ZETL_HOOK_PATH` | Absolute path to the hook executable. Render-pipeline only. | | `ZETL_EXTENSION_ID` | Hook extension id (filename stem, minus `\\d+-` ordering prefix). Render-pipeline only. | | `ZETL_HOOK_DEPTH` | Nesting depth; zetl increments on every child invocation to detect runaway recursion. | | `ZETL_AT` | Time expression passed via `--at`. Set only when time-travel is active. | Pin your hook against `ZETL_AST_SCHEMA_VERSION`, not `ZETL_VERSION` — the AST schema changes far less often. See [[Render Pipeline Hooks]]. Persistent-mode hooks also receive both versions in their JSON init message, alongside a capability probe. See `tools/zetl-ast-reference.md` in the source tree for the wire protocol."},{"h":"Ignore files","l":52,"t":"zetl layers five sources of file-exclusion. Later layers override earlier ones except for level 1 (which is absolute). | Level | Source | Scope | | --- | --- | --- | | 1 | Hardcoded | `.git/`, `.zetl/`, `node_modules/`. Always excluded. Not negatable. | | 2 | Dotdir default | Any top-level directory whose name starts with `.` (`.claude/`, `.obsidian/`, `.cache/`, …). Disable with `--include-hidden`. | | 3 | `.gitignore` | Standard git rules, walked from the vault root. | | 4 | `.zetlignore` | Same syntax as `.gitignore`. zetl-specific overrides of the above. | | 5 | `--exclude <PATTERN>` | Gitignore-syntax, repeatable on the CLI. Overrides everything except level 1. | A `.zetlignore` negation (`!drafts/notes.md`) also overrides the level-2 dotdir default, letting you pull single files out of otherwise-excluded dot-directories without changing git's behaviour. Dotfiles at the top level (e.g. `.editorconfig`) are *not* excluded by the level-2 rule — only directories."},{"h":"`theme.toml`","l":68,"t":"Themes live at `.zetl/themes/<name>/theme.toml` for installed themes, or bundled inside the binary at build time. See [[Customising the Look]]."},{"h":"`[theme]`","l":72,"t":"| Key | Purpose | | --- | --- | | `name` | Theme name. Matches the directory name. | | `version` | SemVer. | | `description` | Optional one-line description. | | `author`, `license`, `homepage`, `min_zetl_version` | Metadata. Optional. |"},{"h":"`[theme.templates]`","l":81,"t":"Tells zetl which template files this theme provides. Anything not listed falls back to the default theme."},{"h":"`[[theme.hooks]]`","l":90,"t":"Array-of-tables declaring hooks shipped under the theme's `hooks/` directory. Under `--safe-mode`, only declared hooks run; everything else (and every vault hook) is skipped. `stage` is `pre-parse`, `transform`, or `post-render`. `extension_id` matches the hook executable's filename stem (minus any `\\d+-` ordering prefix)."},{"h":"`[spa]`","l":108,"t":"Enables the persistent-shell SPA navigation module in the default theme. The element carrying `data-zetl-volatile` is swapped on same-origin `<a>` clicks instead of triggering a full reload. Sidebar and graph stay mounted."},{"h":"`[graph]`","l":117,"t":"`graph_inline = true` at the top level opts the theme into inlined graph JSON on every render (the page's local graph is serialised into the template context)."},{"h":"`[vendor.*]`","l":126,"t":"Pinned client-side assets — `sigma`, `graphology`, `graphology-layout-forceatlas2`. Each entry locks a version, source URL, bundled filename, SHA-256, and license. `zetl build` fails loudly if an asset hash doesn't match."},{"h":"The `.zetl/` directory","l":130,"t":"zetl never writes to your Markdown files. Everything it caches, derives, or persists lives under `.zetl/` at the vault root. Safe to delete — it'll rebuild. | Path | Contents | | --- | --- | | `.zetl/index.json` | Cached link graph. `zetl index` writes it. | | `.zetl/theory.json` | Compiled SPL theory (requires `reason`). | | `.zetl/search/` | Tantivy BM25 index (one segment per `.fast`/`.idx`/`.store`/etc. shard). | | `.zetl/search/vectors/` | Semantic-search embeddings (requires `semantic`). | | `.zetl/history/` | Snapshot metadata JSON files (requires `history`). | | `.zetl/jj/` | Jujutsu repo backing the silent snapshots (requires `history`). | | `.zetl/themes/` | Installed (non-bundled) themes. | | `.zetl/hooks/` | Vault-local hook executables and their sidecar manifests (`<name>.<ext>.toml`). | | `.zetl/collab/server.key` | Multi-user signing key. Derived from `ZETL_SERVER_KEY_SEED` when set. | | `.zetl/crdt/` | Peritext CRDT documents, one per page under `--collab`. | | `.zetl/users/` | Per-user records (passkey credentials, roles). | | `.zetl/models/` | Cached ONNX models + tokenisers (semantic search, agents). | | `.zetl/caps/` | Capability-URL cohorts, grants, and pairing nonces. | Add `.zetl/` to your `.gitignore` unless you have a specific reason to commit the cache (e.g. reproducible search-index tests)."},{"h":"Related","l":152,"t":"- [[CLI Overview]] - [[Installation]] - [[Customising the Look]] - [[Frontmatter Fields]] - [[Snapshots Under the Hood]]"}],"tf":{"1":3,"2":3,"256":1,"3":1,"4":1,"5":1,"a":7,"above":1,"absolute":3,"active":2,"add":1,"against":1,"agents":1,"alongside":1,"also":3,"always":1,"an":1,"and":7,"ansi":1,"any":3,"anything":1,"are":2,"array":1,"as":1,"asset":1,"assets":1,"ast":2,"at":5,"back":1,"backing":1,"bare":1,"behaviour":1,"binary":3,"bip39":1,"bm25":1,"both":1,"build":2,"bundled":3,"by":2,"cache":1,"cached":2,"caches":1,"canonical":1,"capability":3,"carrying":1,"change":1,"changes":1,"changing":1,"child":1,"cli":3,"clicks":1,"client":1,"cohorts":1,"colour":1,"commit":1,"compiled":1,"configuration":2,"contents":1,"context":1,"corresponding":1,"crdt":1,"credentials":1,"customising":2,"de":1,"declared":1,"declaring":1,"default":5,"defaults":1,"delete":1,"deployments":1,"depth":1,"derivation":1,"derived":1,"derives":1,"description":1,"detect":1,"deterministic":1,"directories":2,"directory":7,"disable":2,"documents":1,"doesn":1,"dot":1,"dotdir":2,"dotfiles":1,"e":5,"each":1,"earlier":1,"element":1,"else":1,"embeddings":1,"empty":2,"enables":1,"entry":1,"environment":2,"ephemeral":1,"equivalent":1,"etc":1,"every":5,"everything":3,"except":2,"excluded":3,"exclusion":1,"executable":2,"executables":1,"exported":1,"exposes":1,"expression":1,"extension":1,"facto":1,"fails":1,"falls":1,"far":1,"fields":1,"file":1,"filename":3,"files":6,"five":1,"flag":2,"for":5,"force":1,"forever":1,"from":2,"frontmatter":1,"full":2,"g":5,"git":2,"gitignore":1,"grants":1,"graph":4,"hardcoded":1,"harness":1,"hash":1,"have":1,"here":1,"honours":1,"hood":1,"hook":9,"hooks":5,"hostname":1,"id":1,"if":2,"ignore":2,"in":5,"increments":1,"independent":1,"index":2,"inherits":1,"init":1,"inlined":1,"inside":1,"installation":1,"installed":2,"instead":1,"into":2,"invocation":1,"is":7,"it":3,"json":3,"jujutsu":1,"key":3,"knob":1,"later":1,"launches":1,"layers":2,"less":1,"letting":1,"level":8,"license":1,"line":1,"link":1,"listed":1,"live":1,"lives":1,"ll":1,"local":2,"locks":1,"look":2,"loudly":1,"manifests":1,"markdown":2,"match":1,"matches":2,"message":1,"metadata":2,"minus":2,"mnemonic":1,"mode":1,"models":1,"module":1,"mounted":1,"multi":1,"name":5,"navigation":1,"negatable":1,"negation":1,"nesting":1,"never":1,"no":1,"non":1,"nonces":1,"not":4,"nothing":1,"of":8,"often":1,"on":9,"once":1,"one":3,"ones":1,"only":5,"onnx":1,"optional":2,"opts":1,"or":5,"ordering":2,"origin":1,"otherwise":1,"out":1,"output":1,"override":1,"overrides":3,"overview":1,"page":3,"pairing":1,"party":1,"pass":1,"passed":1,"passkey":1,"path":3,"per":3,"peritext":1,"persistent":2,"persists":1,"phrase":1,"pin":1,"pinned":2,"pipeline":3,"prefix":2,"prefixed":1,"probe":1,"profile":1,"protocol":1,"provides":1,"pull":1,"purpose":2,"read":2,"reason":1,"rebuild":1,"receive":1,"records":1,"recursion":1,"related":1,"reload":1,"relying":1,"render":4,"rendering":1,"repeatable":1,"repo":1,"reproducible":1,"required":1,"requires":4,"rescan":1,"roles":1,"root":3,"rule":1,"rules":1,"run":1,"runaway":1,"runs":1,"s":4,"safe":1,"same":2,"schema":2,"scope":1,"search":3,"see":3,"segment":1,"semantic":2,"semver":2,"serialised":1,"set":2,"sha":1,"shard":1,"shell":2,"shipped":1,"side":1,"sidebar":1,"sidecar":1,"signing":1,"silent":1,"single":1,"site":1,"skip":1,"skipped":1,"snapshot":1,"snapshots":2,"source":3,"sources":1,"spa":1,"spake2":1,"specific":3,"spl":1,"standard":2,"starts":1,"state":1,"stay":1,"stem":2,"string":2,"swapped":1,"syntax":2,"t":1,"tables":1,"tantivy":1,"tells":1,"template":2,"test":1,"tests":1,"the":34,"their":2,"theme":8,"themes":3,"theory":1,"these":2,"this":2,"time":3,"to":10,"tokenisers":1,"top":3,"travel":1,"tree":1,"triggering":1,"under":6,"unless":1,"url":4,"user":2,"value":1,"variable":2,"variables":3,"vault":6,"version":3,"versions":1,"via":1,"walked":1,"want":1,"webauthn":1,"when":3,"which":2,"whose":1,"wire":1,"with":3,"without":1,"writes":2,"you":3,"your":4,"zero":1,"zetl":10}},{"dl":1365,"n":"Troubleshooting","s":"help/troubleshooting","secs":[{"h":"Troubleshooting","l":6,"t":"Things that go wrong, and what to do. Each entry is **Symptom → Diagnosis → Fix**. Use the table of contents to jump; most issues resolve in one or two steps."},{"h":"Contents","l":10,"t":"- [Dead-link warnings for pages I haven't written yet](#dead-link-warnings-for-pages-i-havent-written-yet) - [`zetl reason` prints an error about not being compiled in](#zetl-reason-prints-an-error-about-not-being-compiled-in) - [`zetl --at ...` fails or is not recognised](#zetl---at--fails-or-is-not-recognised) - [`.obsidian/` or `.claude/` contents aren't indexed](#obsidian-or-claude-contents-arent-indexed) - [Terminal viewer shows a single pane instead of two](#terminal-viewer-shows-a-single-pane-instead-of-two) - [`zetl serve` won't start on port 3000](#zetl-serve-wont-start-on-port-3000) - [My hooks aren't running](#my-hooks-arent-running) - [Safe-mode blocks my hooks](#safe-mode-blocks-my-hooks) - [`zetl index` feels slow on a large vault](#zetl-index-feels-slow-on-a-large-vault) - [Collaborative edits conflict or look stale](#collaborative-edits-conflict-or-look-stale) - [I lost my passkey](#i-lost-my-passkey) - [Capability-URL reader errors](#capability-url-reader-errors) - [Getting a useful bug report](#getting-a-useful-bug-report) ---"},{"h":"Dead-link warnings for pages I haven't written yet","l":28,"t":"**Symptom.** `zetl check` reports `[[Future Note]]` as a dead link, even though you intended to write that page later. **Diagnosis.** Not a bug. zetl flags every `[[wikilink]]` whose target doesn't yet exist as a dead link — that's the definition. Many people use this as a \"stub list\" of pages to write. **Fix.** - Create the target page (even as a one-liner) when you're ready. - If you want to suppress a cluster of planned stubs, put them under a folder that's ignored: add the folder to `.zetlignore` with gitignore-style syntax. - Scope a check to ignore them for a single run: `zetl check --fail-on error` only fails on syntax/SPL errors, not dead links. See [[Finding Orphans and Dead Links]] for the full workflow. ---"},{"h":"`zetl reason` prints an error about not being compiled in","l":44,"t":"**Symptom.** Running `zetl reason status` prints a message explaining that the reasoning feature isn't compiled in, and exits non-zero. **Diagnosis.** zetl's reasoning layer is gated behind a Cargo feature flag to keep the core binary small. Your install didn't enable it. **Fix.** Reinstall with the `reason` feature: See [[Installation]] for the complete feature matrix. The same pattern applies to the `history`, `semantic`, and `mcp` flags. ---"},{"h":"`zetl --at ...` fails or is not recognised","l":60,"t":"**Symptom.** `zetl --at \"3 days ago\" links \"Some Page\"` fails or acts as if `--at` were ignored. **Diagnosis.** Time-travel queries require the `history` feature. **Fix.** History uses jj-backed snapshots stored in `.zetl/jj/` — not your repo's `.git/`. Once installed, `--at` works on every read-only subcommand. See [[Time Travel]]. ---"},{"h":"`.obsidian/` or `.claude/` contents aren't indexed","l":76,"t":"**Symptom.** Files under `.obsidian/`, `.claude/`, `.vscode/`, or similar dot-directories don't appear in search, backlinks, or the graph. **Diagnosis.** zetl skips directories whose name starts with `.` by default. This keeps editor metadata, agent working files, and VCS internals out of your knowledge graph. **Fix.** Two options: - Include all hidden directories for one run: `zetl index --include-hidden`. - Re-include a specific dotdir permanently by negating it in `.zetlignore`: Run `zetl --verbose index` to see which paths were skipped and why. See [[Configuration]] for the full precedence stack. ---"},{"h":"Terminal viewer shows a single pane instead of two","l":96,"t":"**Symptom.** `zetl view \"Some Page\"` opens a single-column layout instead of the two-pane reader with context cards on the right. **Diagnosis.** Your terminal is narrower than 60 columns. The two-pane viewer falls back to single-pane in narrow windows. **Fix.** Resize the terminal to at least 60 columns (80+ is more comfortable). You can also adjust the main-pane width: See [[Terminal Viewer]] for keybindings and layout options. ---"},{"h":"`zetl serve` won't start on port 3000","l":112,"t":"**Symptom.** `zetl serve` exits with an address-in-use error. **Diagnosis.** Another process is bound to port 3000. **Fix.** Pick a different port: If you want to know which process is holding 3000: `lsof -i :3000` on macOS/Linux. See [[Web Server]]. ---"},{"h":"My hooks aren't running","l":128,"t":"**Symptom.** You added a script to `.zetl/hooks/post-build`, but `zetl build` completes without running it. **Diagnosis.** Almost always one of three things: 1. The file isn't marked executable. 2. The file is named incorrectly (no extension — just the lifecycle name). 3. It's not in the right directory (`.zetl/hooks/` for vault, `.zetl/themes/<theme>/hooks/` for theme). **Fix.** `zetl hook list` prints every hook zetl sees, with the source (vault or theme) and the lifecycle name. If your hook isn't listed, zetl isn't seeing it. See [[Lifecycle Hooks]]. ---"},{"h":"Safe-mode blocks my hooks","l":150,"t":"**Symptom.** Running with `--safe-mode`, hooks that normally work are skipped. **Diagnosis.** Expected. `--safe-mode` skips every vault hook unconditionally and only runs theme hooks that are explicitly declared in the theme's `[[theme.hooks]]` manifest table. It's the intended posture for previewing untrusted vaults. **Fix.** Two paths: - If you trust the vault, drop `--safe-mode`. - If you're writing a theme that should work under safe-mode, declare each hook in `theme.toml`: See [[Render Pipeline Hooks]] and `docs/hook-security.md` in the source tree for the full security model. ---"},{"h":"`zetl index` feels slow on a large vault","l":172,"t":"**Symptom.** Index times climb as the vault grows. **Diagnosis.** The cache is two-tier: mtime first, then BLAKE3 content hash. Unchanged files should be skipped almost instantly on subsequent runs. If every index is slow, either the cache is being bypassed or every file really has changed. **Fix.** - Don't use `--no-cache` unless you genuinely need a full rescan (e.g. after upgrading zetl across a cache-format bump). It forces every file to re-parse. - Run with `-v` or `-vv` to see per-file scan decisions: - Check for a tool that's rewriting mtimes on every file (some sync clients do this). Those invalidate the first-tier cache on every run; the hash tier still saves reparsing, but the file list scan grows. ---"},{"h":"Collaborative edits conflict or look stale","l":193,"t":"**Symptom.** In `--collab` mode, your edits don't appear for another user, or you see a version from an hour ago. **Diagnosis.** CRDT merges are eventual. If the WebSocket dropped (network blip, laptop sleep), the two sides resync on reconnection. A stale render usually means a browser tab missed reconnection. **Fix.** Refresh the page. The CRDT state on disk is authoritative. If a refresh doesn't help, confirm the server is still running and the WebSocket reconnected (browser devtools → Network → WS). For longer outages, every save in collab mode auto-commits to git with author attribution, so nothing is lost — you can `git log` the vault to reconstruct. See [[Co-editing]]. ---"},{"h":"I lost my passkey","l":205,"t":"**Symptom.** You've cleared browser data, changed devices, or lost your hardware key, and you can't sign in to the collab server. **Diagnosis.** Passkeys are device-bound. The 12-word BIP39 mnemonic printed when you ran `--init-owner` is the recovery path. **Fix.** Open `/auth/recovery` on the running server, enter your display name and the 12-word phrase, and register a new passkey when prompted. See [[Passkeys and Accounts]]. If you never saved the mnemonic: another admin on the same vault can re-invite you. If you were the only admin, the vault files are still on disk — you can stand up a fresh collab server on the same directory and re-bootstrap as owner. ---"},{"h":"Capability-URL reader errors","l":217,"t":"**Symptom.** You opened a capability-URL static site and the shim renders a red error banner — \"signature did not verify\", \"could not decrypt\", etc. **Diagnosis.** These errors are thrown by the in-browser shim before any decryption happens. They're described in detail in `docs/reader-troubleshooting.md` in the source tree. The short version: most of them mean *contact your wiki operator*, not *fix your browser*. **Fix.** The single rule: never override a signature check or paste secrets into a console on a wiki operator's advice. Close the tab and contact them. Quote the error kind (the slug after `err-`) and the URL prefix before `#`. See [[Capability URLs]]. ---"},{"h":"Getting a useful bug report","l":227,"t":"When filing an issue, include: Also note: - Which feature flags were enabled at install (`--features reason,history,...`). This is the single most useful piece of context — many \"missing command\" reports are feature-flag mismatches. - OS and architecture (`uname -a` on macOS/Linux). - Whether the bug reproduces from a fresh clone or only in your existing vault. - The exact command you ran and its full output (stdout + stderr). File issues at **<https://codeberg.org/anuna/zetl>**. For questions that aren't bugs — \"does zetl do X?\" — check [[FAQ]] first."},{"h":"Related","l":246,"t":"- [[FAQ]] - [[Installation]] - [[CLI Overview]] - [[Configuration]] - [[Lifecycle Hooks]]"}],"tf":{"1":1,"12":2,"2":1,"3":1,"3000":5,"60":2,"80":1,"a":40,"about":3,"accounts":1,"across":1,"acts":1,"add":1,"added":1,"address":1,"adjust":1,"admin":2,"advice":1,"after":2,"agent":1,"ago":1,"all":1,"almost":2,"also":2,"always":1,"an":6,"and":21,"another":3,"anuna":1,"any":1,"appear":2,"applies":1,"architecture":1,"are":7,"aren":5,"arent":2,"as":7,"at":4,"attribution":1,"author":1,"authoritative":1,"auto":1,"back":1,"backed":1,"backlinks":1,"banner":1,"be":1,"before":2,"behind":1,"being":4,"binary":1,"bip39":1,"blake3":1,"blip":1,"blocks":3,"bootstrap":1,"bound":2,"browser":5,"bug":5,"bugs":1,"bump":1,"but":2,"by":3,"bypassed":1,"cache":4,"can":5,"capability":5,"cards":1,"cargo":1,"changed":2,"check":4,"claude":1,"cleared":1,"cli":1,"clients":1,"climb":1,"clone":1,"close":1,"cluster":1,"co":1,"codeberg":1,"collab":3,"collaborative":3,"column":1,"columns":2,"comfortable":1,"command":2,"commits":1,"compiled":4,"complete":1,"completes":1,"configuration":2,"confirm":1,"conflict":3,"console":1,"contact":2,"content":1,"contents":5,"context":2,"core":1,"could":1,"crdt":2,"create":1,"data":1,"dead":7,"decisions":1,"declare":1,"declared":1,"decrypt":1,"decryption":1,"default":1,"definition":1,"described":1,"detail":1,"device":1,"devices":1,"devtools":1,"diagnosis":13,"did":1,"didn":1,"different":1,"directories":3,"directory":2,"disk":2,"display":1,"do":3,"does":1,"doesn":2,"don":3,"dot":1,"dotdir":1,"drop":1,"dropped":1,"e":1,"each":2,"editing":1,"editor":1,"edits":4,"either":1,"enable":1,"enabled":1,"enter":1,"entry":1,"error":6,"errors":5,"etc":1,"even":2,"eventual":1,"every":10,"exact":1,"executable":1,"exist":1,"existing":1,"exits":2,"expected":1,"explaining":1,"explicitly":1,"extension":1,"fails":5,"falls":1,"faq":2,"feature":7,"feels":3,"file":8,"files":4,"filing":1,"finding":1,"first":3,"fix":14,"flag":2,"flags":3,"folder":2,"for":17,"forces":1,"format":1,"fresh":2,"from":2,"full":5,"g":1,"gated":1,"genuinely":1,"getting":3,"git":1,"gitignore":1,"go":1,"graph":2,"grows":2,"happens":1,"hardware":1,"has":1,"hash":2,"haven":2,"havent":1,"help":1,"hidden":1,"history":1,"holding":1,"hook":4,"hooks":11,"hour":1,"https":1,"i":6,"if":11,"ignore":1,"ignored":2,"in":22,"include":3,"incorrectly":1,"index":3,"indexed":3,"install":2,"installation":2,"installed":1,"instantly":1,"instead":4,"intended":2,"internals":1,"into":1,"invalidate":1,"invite":1,"is":18,"isn":4,"issue":1,"issues":2,"it":7,"its":1,"jj":1,"jump":1,"just":1,"keep":1,"keeps":1,"key":1,"keybindings":1,"kind":1,"know":1,"knowledge":1,"laptop":1,"large":3,"later":1,"layer":1,"layout":2,"least":1,"lifecycle":4,"liner":1,"link":5,"links":2,"linux":2,"list":2,"listed":1,"longer":1,"look":3,"lost":5,"macos":2,"main":1,"manifest":1,"many":2,"marked":1,"matrix":1,"mean":1,"means":1,"merges":1,"message":1,"metadata":1,"mismatches":1,"missed":1,"missing":1,"mnemonic":2,"mode":6,"model":1,"more":1,"most":3,"mtime":1,"mtimes":1,"my":9,"name":4,"named":1,"narrow":1,"narrower":1,"need":1,"negating":1,"network":2,"never":2,"new":1,"no":1,"non":1,"normally":1,"not":13,"note":1,"nothing":1,"obsidian":1,"of":11,"on":21,"once":1,"one":4,"only":5,"open":1,"opened":1,"opens":1,"operator":2,"options":2,"or":20,"org":1,"orphans":1,"os":1,"out":1,"outages":1,"output":1,"override":1,"overview":1,"owner":1,"page":3,"pages":4,"pane":7,"parse":1,"passkey":4,"passkeys":2,"paste":1,"path":1,"paths":2,"pattern":1,"people":1,"per":1,"permanently":1,"phrase":1,"pick":1,"piece":1,"pipeline":1,"planned":1,"port":5,"posture":1,"precedence":1,"prefix":1,"previewing":1,"printed":1,"prints":5,"process":2,"prompted":1,"put":1,"queries":1,"questions":1,"quote":1,"ran":2,"re":7,"read":1,"reader":4,"ready":1,"really":1,"reason":1,"reasoning":2,"recognised":3,"reconnected":1,"reconnection":2,"reconstruct":1,"recovery":1,"red":1,"refresh":2,"register":1,"reinstall":1,"related":1,"render":2,"renders":1,"reparsing":1,"repo":1,"report":3,"reports":2,"reproduces":1,"require":1,"rescan":1,"resize":1,"resolve":1,"resync":1,"rewriting":1,"right":2,"rule":1,"run":5,"running":8,"runs":2,"s":9,"safe":4,"same":3,"save":1,"saved":1,"saves":1,"scan":2,"scope":1,"script":1,"search":1,"secrets":1,"security":1,"see":14,"seeing":1,"sees":1,"serve":1,"server":5,"shim":2,"short":1,"should":2,"shows":3,"sides":1,"sign":1,"signature":2,"similar":1,"single":8,"site":1,"skipped":3,"skips":2,"sleep":1,"slow":4,"slug":1,"small":1,"snapshots":1,"so":1,"some":1,"source":3,"specific":1,"spl":1,"stack":1,"stale":4,"stand":1,"start":3,"starts":1,"state":1,"static":1,"stderr":1,"stdout":1,"steps":1,"still":3,"stored":1,"stub":1,"stubs":1,"style":1,"subcommand":1,"subsequent":1,"suppress":1,"symptom":13,"sync":1,"syntax":2,"t":20,"tab":2,"table":2,"target":2,"terminal":6,"than":1,"that":10,"the":65,"them":4,"theme":5,"then":1,"these":1,"they":1,"things":2,"this":4,"those":1,"though":1,"three":1,"thrown":1,"tier":3,"time":2,"times":1,"to":20,"tool":1,"travel":2,"tree":2,"troubleshooting":1,"trust":1,"two":10,"unchanged":1,"unconditionally":1,"under":3,"unless":1,"untrusted":1,"up":1,"upgrading":1,"url":5,"urls":1,"use":4,"useful":4,"user":1,"uses":1,"usually":1,"vault":12,"vaults":1,"vcs":1,"ve":1,"verify":1,"version":2,"viewer":5,"want":2,"warnings":3,"web":1,"websocket":2,"were":4,"what":1,"when":4,"whether":1,"which":3,"whose":2,"why":1,"width":1,"wiki":2,"windows":1,"with":9,"without":1,"won":2,"wont":1,"word":2,"work":2,"workflow":1,"working":1,"works":1,"write":2,"writing":1,"written":3,"wrong":1,"ws":1,"x":1,"yet":4,"you":20,"your":11,"zero":1,"zetl":12}},{"dl":800,"n":"FAQ","s":"help/faq","secs":[{"h":"FAQ","l":6,"t":"Answers to questions people actually ask before they install zetl. Each answer is short; follow the link for depth."},{"h":"Does zetl modify my files?","l":10,"t":"No. zetl treats vault files as read-only. The only directory zetl writes to is `.zetl/` at the vault root — the index, semantic model cache, optional jj snapshots, and (in collab mode) the auth and CRDT state all live there. The whole `.zetl/` directory is disposable: delete it, rerun `zetl index`, and you're back where you started. See [[Local-first]] and [[Vaults]]."},{"h":"Is zetl compatible with Obsidian / Logseq / Foam / Dendron?","l":16,"t":"Yes. zetl shares the `[[wikilink]]` syntax those tools use. Point zetl at an existing Obsidian or Logseq vault and `zetl index` just works — nothing in `.obsidian/` or `logseq/` is touched. You can keep editing in your current tool and use zetl for search, reasoning, history, or static-site export. Import-specific notes live at [[Migrating from Obsidian]]."},{"h":"Can I use zetl offline?","l":22,"t":"Yes. Everything — parsing, graph queries, reasoning, the web UI, collab, the terminal reader — runs locally. The only network activity by default is the optional one-time semantic-model download when you first use semantic search; everything else is file-system-local. You can use zetl on a plane. See [[Local-first]]."},{"h":"Do I need to learn Lisp to use zetl?","l":28,"t":"No. The graph features — wikilinks, backlinks, search, blocks, embeds, the web UI, collab — require no Lisp at all. Spindle Lisp (SPL) is an optional reasoning layer gated behind `--features reason`. Write plain Markdown, run plain zetl, and you never touch SPL. If you're curious: [[What is SPL]]."},{"h":"Can I use zetl without a git repo?","l":34,"t":"Yes. zetl never requires `.git/`. If you enable the history feature, temporal snapshots use jj stored in `.zetl/jj/` — a separate VCS from your repo's `.git/`. You can have both, either, or neither. In collab mode, saves auto-commit to a git repo if one exists; without one, edits still persist to disk and through the CRDT. See [[Snapshots Under the Hood]]."},{"h":"Does zetl support Windows?","l":40,"t":"zetl is written in portable Rust and targets Linux, macOS, and Windows. Build from source with a Rust toolchain. Some collab-mode features (passkey flows, WebSocket behaviour) are most exercised on Linux and macOS; file an issue if a Windows-specific rough edge bites. See [[Installation]]."},{"h":"How big a vault does zetl scale to?","l":46,"t":"zetl uses incremental caching — a two-tier (mtime + BLAKE3 hash) index — so re-runs only reparse changed files. Real-world scaling numbers aren't published; in practice the design aim is \"your vault, however big it grows, stays snappy\". If you hit a slow spot, [[Troubleshooting]] has a section on it and the issue tracker wants to hear."},{"h":"Can multiple people use the same vault?","l":50,"t":"Two models: 1. **Live collab server** — run `zetl serve --collab` and invite collaborators with passkeys. Edits sync in real time through a CRDT engine; every save auto-commits to git with author attribution. See [[Running a Team Server]]. 2. **Shared git repo** — commit your vault to git and let collaborators clone, edit, and push as they would any other text repo. zetl doesn't care whether the file on disk came from a human, a collaborator, or a merge commit. You can combine the two."},{"h":"What's the difference between a lifecycle hook and a render-pipeline hook?","l":59,"t":"**Lifecycle hooks** run at coarse events — `pre-build`, `post-build`, `on-save`, etc. They get the whole vault context as JSON on stdin, run once per event, and are ideal for tasks like generating an RSS feed or posting to a webhook. **Render-pipeline hooks** run inside the build, per page, at one of three fine-grained stages — `pre-parse`, `transform`, or `post-render`. They're persistent subprocesses speaking a JSON-lines protocol, operate on a typed AST, and are the right tool for custom Markdown extensions, callouts, or pandoc integration. Details: [[Lifecycle Hooks]] and [[Render Pipeline Hooks]]."},{"h":"Is there a plugin marketplace?","l":67,"t":"Not yet. Two things take the pressure off until there is: - zetl ships first-class adapters for **Pandoc**, **mdBook**, and **remark** — three huge existing plugin ecosystems. You can run a Pandoc Lua filter as a render-pipeline hook with a four-line manifest. - The hook authoring CLI (`zetl hook new/test/watch`) makes writing your own a few-minute task, not a weekend project. See [[Plugin Ecosystems]]."},{"h":"What about AI agents?","l":76,"t":"zetl exposes graph, search, and reasoning as typed tools over the Model Context Protocol. Build with `--features mcp`, run `zetl mcp` over stdio or HTTP, and issue scoped delegate tokens to your agent. See [[MCP Server]] and [[Plugin Ecosystems]]."},{"h":"Is zetl free?","l":82,"t":"Yes. zetl is released under **AGPL-3.0-or-later**. The source is at <https://codeberg.org/anuna/zetl>."},{"h":"Where does zetl store its data?","l":86,"t":"Everything zetl writes lives in `.zetl/` next to your vault: All of it is disposable. Delete `.zetl/` and your notes are untouched; a fresh `zetl index` rebuilds the cache from scratch. See [[Configuration]]."},{"h":"Related","l":104,"t":"- [[Troubleshooting]] - [[What is zetl]] - [[Installation]] - [[Quick Start]] - [[Local-first]]"}],"tf":{"0":1,"1":1,"2":1,"3":1,"a":27,"about":1,"activity":1,"actually":1,"adapters":1,"agent":1,"agents":1,"agpl":1,"ai":1,"aim":1,"all":3,"an":4,"and":24,"answer":1,"answers":1,"anuna":1,"any":1,"are":4,"aren":1,"as":5,"ask":1,"ast":1,"at":7,"attribution":1,"auth":1,"author":1,"authoring":1,"auto":2,"back":1,"backlinks":1,"before":1,"behaviour":1,"behind":1,"between":1,"big":2,"bites":1,"blake3":1,"blocks":1,"both":1,"build":3,"by":1,"cache":2,"caching":1,"callouts":1,"came":1,"can":8,"care":1,"changed":1,"class":1,"cli":1,"clone":1,"coarse":1,"codeberg":1,"collab":6,"collaborator":1,"collaborators":2,"combine":1,"commit":3,"commits":1,"compatible":1,"configuration":1,"context":2,"crdt":3,"curious":1,"current":1,"custom":1,"data":1,"default":1,"delegate":1,"delete":2,"dendron":1,"depth":1,"design":1,"details":1,"difference":1,"directory":2,"disk":2,"disposable":2,"do":1,"does":4,"doesn":1,"download":1,"each":1,"ecosystems":3,"edge":1,"edit":1,"editing":1,"edits":2,"either":1,"else":1,"embeds":1,"enable":1,"engine":1,"etc":1,"event":1,"events":1,"every":1,"everything":3,"exercised":1,"existing":2,"exists":1,"export":1,"exposes":1,"extensions":1,"faq":1,"feature":1,"features":2,"feed":1,"few":1,"file":3,"files":3,"filter":1,"fine":1,"first":5,"flows":1,"foam":1,"follow":1,"for":5,"four":1,"free":1,"fresh":1,"from":5,"gated":1,"generating":1,"get":1,"git":5,"grained":1,"graph":3,"grows":1,"has":1,"hash":1,"have":1,"hear":1,"history":2,"hit":1,"hood":1,"hook":4,"hooks":4,"how":1,"however":1,"http":1,"https":1,"huge":1,"human":1,"i":3,"ideal":1,"if":5,"import":1,"in":9,"incremental":1,"index":2,"inside":1,"install":1,"installation":2,"integration":1,"invite":1,"is":18,"issue":3,"it":4,"its":1,"jj":2,"json":2,"just":1,"keep":1,"later":1,"layer":1,"learn":1,"let":1,"lifecycle":3,"like":1,"line":1,"lines":1,"link":1,"linux":2,"lisp":3,"live":3,"lives":1,"local":4,"locally":1,"logseq":2,"lua":1,"macos":2,"makes":1,"manifest":1,"markdown":2,"marketplace":1,"mcp":1,"mdbook":1,"merge":1,"migrating":1,"minute":1,"mode":3,"model":3,"models":1,"modify":1,"most":1,"mtime":1,"multiple":1,"my":1,"need":1,"neither":1,"network":1,"never":2,"next":1,"no":3,"not":2,"notes":2,"nothing":1,"numbers":1,"obsidian":3,"of":2,"off":1,"offline":1,"on":6,"once":1,"one":4,"only":4,"operate":1,"optional":3,"or":10,"org":1,"other":1,"over":2,"own":1,"page":1,"pandoc":3,"parsing":1,"passkey":1,"passkeys":1,"people":2,"per":2,"persist":1,"persistent":1,"pipeline":4,"plain":2,"plane":1,"plugin":4,"point":1,"portable":1,"posting":1,"practice":1,"pressure":1,"project":1,"protocol":2,"published":1,"push":1,"queries":1,"questions":1,"quick":1,"re":4,"read":1,"reader":1,"real":2,"reasoning":4,"rebuilds":1,"related":1,"released":1,"remark":1,"render":4,"reparse":1,"repo":5,"require":1,"requires":1,"rerun":1,"right":1,"root":1,"rough":1,"rss":1,"run":7,"running":1,"runs":2,"rust":2,"s":2,"same":1,"save":1,"saves":1,"scale":1,"scaling":1,"scoped":1,"scratch":1,"search":4,"section":1,"see":8,"semantic":3,"separate":1,"server":3,"shared":1,"shares":1,"ships":1,"short":1,"site":1,"slow":1,"snappy":1,"snapshots":3,"so":1,"some":1,"source":2,"speaking":1,"specific":2,"spindle":1,"spl":3,"spot":1,"stages":1,"start":1,"started":1,"state":1,"static":1,"stays":1,"stdin":1,"stdio":1,"still":1,"store":1,"stored":1,"subprocesses":1,"support":1,"sync":1,"syntax":1,"system":1,"t":2,"take":1,"targets":1,"task":1,"tasks":1,"team":1,"temporal":1,"terminal":1,"text":1,"the":30,"there":3,"they":4,"things":1,"those":1,"three":2,"through":2,"tier":1,"time":2,"to":13,"tokens":1,"tool":2,"toolchain":1,"tools":2,"touch":1,"touched":1,"tracker":1,"treats":1,"troubleshooting":2,"two":4,"typed":2,"ui":2,"under":2,"until":1,"untouched":1,"use":9,"uses":1,"vault":9,"vaults":1,"vcs":1,"wants":1,"web":2,"webhook":1,"websocket":1,"weekend":1,"what":4,"when":1,"where":2,"whether":1,"whole":2,"wikilinks":1,"windows":3,"with":6,"without":2,"works":1,"world":1,"would":1,"write":1,"writes":2,"writing":1,"written":1,"yes":4,"yet":1,"you":12,"your":8,"zetl":27}},{"dl":311,"n":"Embeds and Transclusion","s":"writing/embeds-and-transclusion","secs":[{"h":"Embeds and Transclusion","l":6,"t":"An embed pulls another page's content *into* the current page. A plain wikilink just points. An embed quotes — at full resolution, live, without duplicating the source."},{"h":"The three embed forms","l":12,"t":"The leading `!` is what turns a link into an embed. Everything else mirrors the plain wikilink forms — see [[Wikilinks]]."},{"h":"How embeds render","l":23,"t":"When you run `zetl serve` or `zetl build`, embeds render as **transclusion cards** in a right-rail panel alongside the main content. The surrounding prose keeps flowing; the embedded material appears next to the paragraph that references it, in its own framed card showing the source page, the quoted region, and a link back to the original. In the terminal viewer (`zetl view`), embeds render in the two-pane layout, with the source content expanded where the `![[...]]` appears — see [[Terminal Viewer]]."},{"h":"When to embed, when to link","l":35,"t":"| Situation | Use | |---|---| | You're pointing at related material | plain link `[[...]]` | | You want the reader to *see* the quoted passage without leaving | embed `![[...]]` | | You need a definition, claim, or diagram from elsewhere | embed a block `![[Page^id]]` | | You're writing a meta-page (index, reading list, MOC) | mostly links | | You're writing an essay that quotes your own notes | embeds for quoted material | A good heuristic: embed when you'd otherwise be tempted to copy-paste. Embeds stay in sync if the source changes; copies rot."},{"h":"A worked example","l":48,"t":"`Book Journal/Seeing Like a State.md`: `Essays/Legibility and Craft.md`: The essay quotes the book without duplicating the passage. Update the definition in the book note and every essay that embeds it picks up the change on the next `zetl build`."},{"h":"For theme authors: `page.transclusion_cards`","l":92,"t":"If you're writing a custom theme, the pre-rendered transclusion panel is available as `page.transclusion_cards` in the template context — a string of HTML ready to drop into your layout with `| safe`. See [[Customising the Look]] for theming, and [[Frontmatter Fields]] for the full template variable list."},{"h":"Related","l":100,"t":"- [[Wikilinks]] - [[Headings and Blocks]] - [[Linking Pages]] - [[Customising the Look]] - [[Blocks]]"}],"tf":{"a":11,"alongside":1,"an":4,"and":5,"another":1,"appears":2,"as":2,"at":2,"authors":1,"available":1,"back":1,"be":1,"block":1,"blocks":2,"book":2,"card":1,"cards":1,"change":1,"changes":1,"claim":1,"content":3,"context":1,"copies":1,"copy":1,"current":1,"custom":1,"customising":2,"d":1,"definition":2,"diagram":1,"drop":1,"duplicating":2,"else":1,"elsewhere":1,"embed":8,"embedded":1,"embeds":7,"essay":3,"every":1,"everything":1,"example":1,"expanded":1,"fields":1,"flowing":1,"for":4,"forms":2,"framed":1,"from":1,"frontmatter":1,"full":2,"good":1,"headings":1,"heuristic":1,"how":1,"html":1,"if":2,"in":7,"index":1,"into":3,"is":2,"it":2,"its":1,"just":1,"keeps":1,"layout":2,"leading":1,"leaving":1,"link":4,"linking":1,"links":1,"list":2,"live":1,"look":2,"main":1,"material":3,"meta":1,"mirrors":1,"moc":1,"mostly":1,"need":1,"next":2,"note":1,"notes":1,"of":1,"on":1,"or":2,"original":1,"otherwise":1,"own":2,"page":4,"pages":1,"pane":1,"panel":2,"paragraph":1,"passage":2,"paste":1,"picks":1,"plain":3,"pointing":1,"points":1,"pre":1,"prose":1,"pulls":1,"quoted":3,"quotes":3,"rail":1,"re":4,"reader":1,"reading":1,"ready":1,"references":1,"region":1,"related":2,"render":3,"rendered":1,"resolution":1,"right":1,"rot":1,"run":1,"s":1,"see":4,"showing":1,"situation":1,"source":4,"stay":1,"string":1,"surrounding":1,"sync":1,"template":2,"tempted":1,"terminal":2,"that":3,"the":31,"theme":2,"theming":1,"three":1,"to":7,"transclusion":3,"turns":1,"two":1,"up":1,"update":1,"use":1,"variable":1,"viewer":2,"want":1,"what":1,"when":4,"where":1,"wikilink":2,"wikilinks":2,"with":2,"without":3,"worked":1,"writing":3,"you":8,"your":2}},{"dl":417,"n":"Tags and Frontmatter","s":"writing/tags-and-frontmatter","secs":[{"h":"Tags and Frontmatter","l":6,"t":"Tags are how you group pages without forcing them into folders. Frontmatter is how you attach any other structured data — dates, statuses, authors, book ISBNs, whatever your vault needs — that zetl and your templates can both see."},{"h":"YAML frontmatter basics","l":13,"t":"Frontmatter is a YAML block at the very top of a page, fenced by `---`: Anything inside the fences is frontmatter; everything after the closing `---` is the body. zetl reads frontmatter on every scan and exposes it to templates, search, and hooks. See [[Frontmatter]] for the conceptual model and [[Frontmatter Fields]] for the reserved-key reference."},{"h":"Tagging — two syntaxes","l":37,"t":"zetl recognises tags in two places. **Frontmatter tags.** A YAML list under the `tags:` key: This is the canonical form. Tags here are structured metadata — they're first-class, they show up in templates, and they don't clutter your prose. **Inline hashtags.** `#word` tokens inside the body of the page: Both forms work; zetl surfaces them together. Inline tags are convenient for tagging a paragraph mid-thought; frontmatter tags are better for page-level classification. A typical page uses frontmatter for 2–4 durable tags and skips inline tags entirely."},{"h":"Finding pages by tag","l":63,"t":"There's no dedicated `--tag` flag, but `zetl search` is frontmatter-aware and matches tag text directly: For anything programmatic, export the graph and filter: See [[Searching]] for the full set of search options and [[Following Links]] for graph export."},{"h":"Frontmatter beyond tags","l":83,"t":"zetl treats frontmatter as an open map. You can attach any keys you like, and templates see them via `page.frontmatter.<key>`. Some common patterns: A few reserved keys have special meaning to zetl or to the default theme — `title`, `tags`, `date`, `status`, `draft`, `aliases`. See [[Frontmatter Fields]] for the full list and the semantics. Custom keys are left alone: `attendees`, `rating`, `isbn`, `project` — pick whatever schema fits your vault."},{"h":"Templates see your frontmatter","l":105,"t":"Anything you put in frontmatter is available when `zetl build` renders the page. If your theme's `page.html` includes: then every page that sets `status:` or `date:` renders accordingly. This is how you build a book-journal card, a task status pill, or an RSS feed from page metadata without leaving Markdown. See [[Customising the Look]]."},{"h":"Keep it small","l":123,"t":"Frontmatter is a pointy tool. Two heuristics: - **If it's prose, put it in the body.** Frontmatter is for facts a machine might want to filter on, not for narrative. - **If you add a key, use it.** Dead frontmatter keys that no template or query ever reads just add noise. Grep your theme for `page.frontmatter.` occasionally and prune."},{"h":"Related","l":133,"t":"- [[Frontmatter]] - [[Frontmatter Fields]] - [[Organising Your Vault]] - [[Searching]] - [[Customising the Look]]"}],"tf":{"2":1,"4":1,"a":11,"accordingly":1,"add":2,"after":1,"alone":1,"an":2,"and":13,"any":2,"anything":3,"are":5,"as":1,"at":1,"attach":2,"authors":1,"available":1,"aware":1,"basics":1,"better":1,"beyond":1,"block":1,"body":3,"book":2,"both":2,"build":1,"but":1,"by":2,"can":2,"canonical":1,"card":1,"class":1,"classification":1,"closing":1,"clutter":1,"common":1,"conceptual":1,"convenient":1,"custom":1,"customising":2,"data":1,"dates":1,"dead":1,"dedicated":1,"default":1,"directly":1,"don":1,"durable":1,"entirely":1,"ever":1,"every":2,"everything":1,"export":2,"exposes":1,"facts":1,"feed":1,"fenced":1,"fences":1,"few":1,"fields":3,"filter":2,"finding":1,"first":1,"fits":1,"flag":1,"folders":1,"following":1,"for":12,"forcing":1,"form":1,"forms":1,"from":1,"frontmatter":22,"full":2,"graph":2,"grep":1,"group":1,"hashtags":1,"have":1,"here":1,"heuristics":1,"hooks":1,"how":3,"if":3,"in":4,"includes":1,"inline":3,"inside":2,"into":1,"is":10,"isbns":1,"it":5,"journal":1,"just":1,"keep":1,"key":3,"keys":4,"leaving":1,"left":1,"level":1,"like":1,"links":1,"list":2,"look":2,"machine":1,"map":1,"markdown":1,"matches":1,"meaning":1,"metadata":2,"mid":1,"might":1,"model":1,"narrative":1,"needs":1,"no":2,"noise":1,"not":1,"occasionally":1,"of":3,"on":2,"open":1,"options":1,"or":4,"organising":1,"other":1,"page":7,"pages":2,"paragraph":1,"patterns":1,"pick":1,"pill":1,"places":1,"pointy":1,"programmatic":1,"prose":2,"prune":1,"put":2,"query":1,"re":1,"reads":2,"recognises":1,"reference":1,"related":1,"renders":2,"reserved":2,"rss":1,"s":3,"scan":1,"schema":1,"search":2,"searching":2,"see":7,"semantics":1,"set":1,"sets":1,"show":1,"skips":1,"small":1,"some":1,"special":1,"status":1,"statuses":1,"structured":2,"surfaces":1,"syntaxes":1,"t":1,"tag":2,"tagging":2,"tags":10,"task":1,"template":1,"templates":5,"text":1,"that":3,"the":19,"them":3,"theme":3,"then":1,"there":1,"they":3,"this":2,"thought":1,"to":4,"together":1,"tokens":1,"tool":1,"top":1,"treats":1,"two":3,"typical":1,"under":1,"up":1,"use":1,"uses":1,"vault":3,"very":1,"via":1,"want":1,"whatever":2,"when":1,"without":2,"work":1,"yaml":3,"you":7,"your":8,"zetl":6}},{"dl":409,"n":"Organising Your Vault","s":"writing/organising-your-vault","secs":[{"h":"Organising Your Vault","l":6,"t":"zetl is indifferent to how your vault is laid out. Pages resolve by filename, not path, so you can reorganise at any time without breaking links."},{"h":"No required structure","l":11,"t":"A handful of layouts people actually use: **Flat.** Every page in the vault root. Works surprisingly well up to a few hundred pages. The link graph provides the structure; the filesystem is just a blob. **Topic-based.** Top-level folders by subject. **Johnny Decimal.** Numbered categories. Good when the topics are stable and you value predictable paths. **Zettelkasten-style.** Timestamped slip-boxes, no folders, atomic notes linked into trails. Works, but not required — zetl is not a Zettelkasten tool, just a link-graph tool. None of these is \"right.\" Pick whatever matches how your brain looks things up when you're not thinking about tooling."},{"h":"Pragmatic advice: start flat","l":69,"t":"If you're starting a new vault, put everything in the root. When you pass around 100 pages and finding files in your editor gets annoying, group the obvious clusters into folders. You'll know which ones — they're the folders you wish existed."},{"h":"Rename and move freely","l":76,"t":"Because links resolve by filename, moving `Seeing Like a State.md` from `~/notes/` to `~/notes/Books/` doesn't break a single `[[Seeing Like a State]]` reference. After the move: Renaming is a little more work — see [[Linking Pages]] for the rename workflow."},{"h":"`.zetlignore` — excluding drafts and private files","l":89,"t":"zetl scans the entire vault root by default. Put files and folders you want zetl to ignore into a `.zetlignore` file using gitignore syntax: Patterns match the vault root. Subdirectory `.zetlignore` files are not honoured — keep it all in one file at the top. zetl already excludes `.git/`, `.zetl/`, `node_modules/`, and dotdirs like `.obsidian/` by default. Run `zetl build --include-hidden` to include them anyway, or `zetl build --exclude 'pattern/'` for a one-off exclusion that doesn't touch `.zetlignore`."},{"h":"Tags vs folders","l":110,"t":"Both are fine. They answer different questions. | Folders answer | Tags answer | |---|---| | \"Where does this file live?\" | \"What is this file about?\" | | One per page | Many per page | | Exclusive | Overlapping | | Filesystem-native | Metadata-native | A meeting note might live in `Meetings/2026-04-18.md` (one place) and carry `tags: [project-x, priya]` (two dimensions). Folders scale for filesystem browsing; tags scale for cross-cutting queries. See [[Tags and Frontmatter]]."},{"h":"Naming conventions","l":126,"t":"Three patterns pull their weight: - **Titles.** `Zettelkasten Method.md`, not `zettelkasten-method.md`. Human filenames. zetl slugifies at render. - **Dates.** `2026-04-18` (ISO 8601) sorts right and never confuses month order. Use it in journal/meeting filenames. - **Uniqueness.** Two pages with the same title on the graph is ambiguous. Disambiguate in the filename: `Meeting with Priya 2026-04-18.md`, `Scientific Management (book).md`."},{"h":"Related","l":138,"t":"- [[Vaults]] - [[Tags and Frontmatter]] - [[Writing Pages]] - [[Linking Pages]] - [[Configuration]]"}],"tf":{"100":1,"8601":1,"a":11,"about":2,"actually":1,"advice":1,"after":1,"all":1,"already":1,"ambiguous":1,"and":10,"annoying":1,"answer":3,"any":1,"anyway":1,"are":3,"around":1,"at":3,"atomic":1,"based":1,"because":1,"blob":1,"both":1,"boxes":1,"brain":1,"break":1,"breaking":1,"browsing":1,"but":1,"by":5,"can":1,"carry":1,"categories":1,"clusters":1,"configuration":1,"confuses":1,"conventions":1,"cross":1,"cutting":1,"dates":1,"decimal":1,"default":2,"different":1,"dimensions":1,"disambiguate":1,"does":1,"doesn":2,"dotdirs":1,"drafts":1,"editor":1,"entire":1,"every":1,"everything":1,"excludes":1,"excluding":1,"exclusion":1,"exclusive":1,"existed":1,"few":1,"file":4,"filename":3,"filenames":2,"files":4,"filesystem":3,"finding":1,"fine":1,"flat":2,"folders":8,"for":4,"freely":1,"from":1,"frontmatter":2,"gets":1,"gitignore":1,"good":1,"graph":3,"group":1,"handful":1,"honoured":1,"how":2,"human":1,"hundred":1,"if":1,"ignore":1,"in":7,"include":1,"indifferent":1,"into":3,"is":8,"iso":1,"it":2,"johnny":1,"journal":1,"just":2,"keep":1,"know":1,"laid":1,"layouts":1,"level":1,"like":1,"link":2,"linked":1,"linking":2,"links":2,"little":1,"live":2,"ll":1,"looks":1,"many":1,"match":1,"matches":1,"meeting":2,"metadata":1,"might":1,"month":1,"more":1,"move":2,"moving":1,"naming":1,"native":2,"never":1,"new":1,"no":2,"none":1,"not":6,"note":1,"notes":1,"numbered":1,"obvious":1,"of":2,"off":1,"on":1,"one":4,"ones":1,"or":1,"order":1,"organising":1,"out":1,"overlapping":1,"page":3,"pages":7,"pass":1,"path":1,"paths":1,"patterns":2,"people":1,"per":2,"pick":1,"place":1,"pragmatic":1,"predictable":1,"private":1,"provides":1,"pull":1,"put":2,"queries":1,"questions":1,"re":3,"reference":1,"related":1,"rename":2,"renaming":1,"render":1,"reorganise":1,"required":2,"resolve":2,"right":2,"root":4,"run":1,"same":1,"scale":2,"scans":1,"see":2,"single":1,"slip":1,"slugifies":1,"so":1,"sorts":1,"stable":1,"start":1,"starting":1,"structure":2,"style":1,"subdirectory":1,"subject":1,"surprisingly":1,"syntax":1,"t":2,"tags":5,"that":1,"the":16,"their":1,"them":1,"these":1,"they":2,"things":1,"thinking":1,"this":2,"three":1,"time":1,"timestamped":1,"title":1,"titles":1,"to":5,"tool":2,"tooling":1,"top":2,"topic":1,"topics":1,"touch":1,"trails":1,"two":2,"uniqueness":1,"up":2,"use":2,"using":1,"value":1,"vault":6,"vaults":1,"vs":1,"want":1,"weight":1,"well":1,"what":1,"whatever":1,"when":3,"where":1,"which":1,"wish":1,"with":1,"without":1,"work":1,"workflow":1,"works":2,"writing":1,"you":8,"your":4,"zetl":6,"zettelkasten":2}},{"dl":440,"n":"Linking Pages","s":"writing/linking-pages","secs":[{"h":"Linking Pages","l":6,"t":"Links are how your vault stops being a folder of files and starts being a graph. This page covers the everyday workflow of linking — the full syntax reference lives in [[Wikilinks]]."},{"h":"Link as you write","l":12,"t":"The honest workflow: while drafting a note, when you reach a concept that deserves its own page, just wrap it in double brackets. Two things happen: 1. If `Scientific Management.md` already exists, the link resolves — clicking it in `zetl view` or `zetl serve` navigates to the page. 2. If it doesn't exist, zetl flags it as a **dead link** but otherwise leaves you alone. The link is a promise, not an error. Dead links are surfaced by `zetl check --dead-links`: You can create the target page any time. Run `zetl index` (or let `zetl serve` re-scan) and the link goes live."},{"h":"Creating the target page later","l":40,"t":"This is the rhythm most writers settle into: 1. Write your note. Link freely to pages that don't exist yet. 2. When the dead-link list gets long enough to bother you, pick one. 3. Open a file of that name and start writing the page. In `zetl serve`, clicking a dead link opens a \"new page\" stub — the template sees `page.is_new = true` and can render a \"start writing\" affordance. In `zetl view`, a dead link shows as such but won't navigate. You're never *required* to fill in a dead link. Some dead links are aspirational and can stay that way for years."},{"h":"Renaming pages safely","l":55,"t":"zetl resolves links by *filename*, so if you rename a page, every existing link to its old name turns into a dead link. Two approaches: **Rename and fix up.** Rename the file, then `zetl check --dead-links` to find every reference to the old name. Replace them with the new name in your editor's find-and-replace. **Leave a redirect.** Keep a short stub at the old filename: Either works. The second is friendlier if external readers may have the old URL."},{"h":"Link aliases","l":79,"t":"Sometimes the canonical page name reads awkwardly in a sentence. Use the pipe syntax: The link still points to `Annual Review 2026.md`. Only the displayed text changes. Aliases are good for: - Grammatical smoothness (`[[Scientific Management|Taylorism]]`) - Disambiguation (`[[Priya — Meeting 2026-04-18|our 1:1]]`) - Keeping prose readable when the canonical name is long"},{"h":"Heading and block links","l":96,"t":"Two flavours of deep link: Both get their own page — see [[Headings and Blocks]]."},{"h":"A note on link density","l":107,"t":"Err on the side of linking too much. The graph is only as useful as the links in it, and backlinks (see [[Backlinks]]) surface those links back to you later. A page with no outgoing links is a dead end; a page with twenty is a junction."},{"h":"Related","l":113,"t":"- [[Wikilinks]] - [[Embeds and Transclusion]] - [[Headings and Blocks]] - [[Finding Orphans and Dead Links]] - [[Backlinks]]"}],"tf":{"1":2,"2":2,"3":1,"a":22,"affordance":1,"aliases":2,"alone":1,"already":1,"an":1,"and":13,"any":1,"approaches":1,"are":4,"as":5,"aspirational":1,"at":1,"awkwardly":1,"back":1,"backlinks":3,"being":2,"block":1,"blocks":2,"both":1,"bother":1,"brackets":1,"but":2,"by":2,"can":3,"canonical":2,"changes":1,"clicking":2,"concept":1,"covers":1,"create":1,"creating":1,"dead":10,"deep":1,"density":1,"deserves":1,"disambiguation":1,"displayed":1,"doesn":1,"don":1,"double":1,"drafting":1,"editor":1,"either":1,"embeds":1,"end":1,"enough":1,"err":1,"error":1,"every":2,"everyday":1,"exist":2,"existing":1,"exists":1,"external":1,"file":2,"filename":2,"files":1,"fill":1,"find":2,"finding":1,"fix":1,"flags":1,"flavours":1,"folder":1,"for":2,"freely":1,"friendlier":1,"full":1,"get":1,"gets":1,"goes":1,"good":1,"grammatical":1,"graph":2,"happen":1,"have":1,"heading":1,"headings":2,"honest":1,"how":1,"if":4,"in":9,"into":2,"is":7,"it":5,"its":2,"junction":1,"just":1,"keep":1,"keeping":1,"later":2,"leave":1,"leaves":1,"let":1,"link":16,"linking":3,"links":9,"list":1,"live":1,"lives":1,"long":2,"may":1,"most":1,"much":1,"name":6,"navigate":1,"navigates":1,"never":1,"new":2,"no":1,"not":1,"note":3,"of":5,"old":4,"on":2,"one":1,"only":2,"open":1,"opens":1,"or":2,"orphans":1,"otherwise":1,"outgoing":1,"own":2,"page":12,"pages":3,"pick":1,"pipe":1,"points":1,"promise":1,"prose":1,"re":2,"reach":1,"readable":1,"readers":1,"reads":1,"redirect":1,"reference":2,"related":1,"rename":3,"renaming":1,"render":1,"replace":2,"required":1,"resolves":2,"rhythm":1,"run":1,"s":1,"safely":1,"scan":1,"second":1,"see":2,"sees":1,"sentence":1,"settle":1,"short":1,"shows":1,"side":1,"smoothness":1,"so":1,"some":1,"sometimes":1,"start":2,"starts":1,"stay":1,"still":1,"stops":1,"stub":2,"such":1,"surface":1,"surfaced":1,"syntax":2,"t":3,"target":2,"template":1,"text":1,"that":4,"the":27,"their":1,"them":1,"then":1,"things":1,"this":2,"those":1,"time":1,"to":9,"too":1,"transclusion":1,"turns":1,"twenty":1,"two":3,"up":1,"url":1,"use":1,"useful":1,"vault":1,"way":1,"when":3,"while":1,"wikilinks":2,"with":3,"won":1,"workflow":2,"works":1,"wrap":1,"write":2,"writers":1,"writing":2,"years":1,"yet":1,"you":8,"your":3,"zetl":2}},{"dl":423,"n":"Headings and Blocks","s":"writing/headings-and-blocks","secs":[{"h":"Headings and Blocks","l":6,"t":"A plain wikilink takes a reader to a page. On a long page, that's a coarse target — you may have wanted to point at one section, or one sentence. zetl supports both."},{"h":"Heading links","l":12,"t":"Use `#` to point at a heading within a page: Matching rules: - **Case-insensitive.** `#Priorities`, `#priorities`, and `#PRIORITIES` all match `## Priorities`. - **Slugified.** Spaces and punctuation are normalised, so `#getting started` matches `## Getting Started`. - **Any heading level.** `#` in the link matches `##`, `###`, and so on — the link just names the heading text. If multiple headings on a page share the same text, the first one wins. That's rare in practice; rename one of them if it bites you."},{"h":"Block ids","l":33,"t":"When you want to point at something finer than a heading — a definition, a quoted passage, a data row — use a **block id**. Append `^some-id` to the end of the block, then link with `[[Page^some-id]]`: Block ids can live on any paragraph, blockquote, list item, code block, or table. Convention: - Lower-case, hyphenated: `^legibility-def`, `^thesis-claim`, `^q3-numbers` - Short and meaningful - Unique within the page"},{"h":"Why block links matter for long notes","l":60,"t":"Three situations where block ids pay off: **Reference material.** A long glossary entry, a canonical definition, a table of constants — block ids let other pages quote them surgically without copying. **Evolving claims.** A key argument on a draft page gets revised over time. If other pages embed it via `![[Page^id]]` (see [[Embeds and Transclusion]]), every quoting page updates automatically. **Citations to your own notes.** In a research vault, you'll often want to point at \"the thing Alice said on 2026-03-12\" — give that paragraph a block id like `^alice-crdt-comment` and every downstream note can cite it."},{"h":"Stable under edits","l":76,"t":"Block ids are stable even if the block's surrounding page changes. Under the hood, zetl assigns each block a content-addressed identity by hashing its text into a Merkle tree (see [[Blocks]]). When you add a block id, zetl anchors future links to that block's identity — reorder the page, add new paragraphs, rewrite the intro, and `[[Seeing Like a State^legibility-def]]` still resolves to the same block. You can list a page's blocks (with their hashes) using: And resolve a block by hash prefix: Useful when you're debugging a transclusion or writing a reasoning rule (see [[Running Queries]]) against a specific claim."},{"h":"Combining: heading + block","l":101,"t":"A block id wins if the page has both. Link to the heading for \"this section,\" to the block id for \"this specific passage.\" Don't try to combine them (`Page#Section^id`) — zetl parses one or the other."},{"h":"Related","l":107,"t":"- [[Wikilinks]] - [[Blocks]] - [[Embeds and Transclusion]] - [[Linking Pages]] - [[The Link Graph]]"}],"tf":{"03":1,"12":1,"2026":1,"a":29,"add":2,"addressed":1,"against":1,"alice":1,"all":1,"anchors":1,"and":10,"any":2,"append":1,"are":2,"argument":1,"assigns":1,"at":4,"automatically":1,"bites":1,"block":19,"blockquote":1,"blocks":4,"both":2,"by":2,"can":3,"canonical":1,"case":2,"changes":1,"citations":1,"cite":1,"claim":1,"claims":1,"coarse":1,"code":1,"combine":1,"combining":1,"constants":1,"content":1,"convention":1,"copying":1,"data":1,"debugging":1,"definition":2,"don":1,"downstream":1,"draft":1,"each":1,"edits":1,"embed":1,"embeds":2,"end":1,"entry":1,"even":1,"every":2,"evolving":1,"finer":1,"first":1,"for":3,"future":1,"gets":1,"give":1,"glossary":1,"graph":1,"has":1,"hash":1,"hashes":1,"hashing":1,"have":1,"heading":7,"headings":2,"hood":1,"hyphenated":1,"id":5,"identity":2,"ids":5,"if":5,"in":3,"insensitive":1,"into":1,"intro":1,"it":3,"item":1,"its":1,"just":1,"key":1,"let":1,"level":1,"like":1,"link":5,"linking":1,"links":3,"list":2,"live":1,"ll":1,"long":3,"lower":1,"match":1,"matches":2,"matching":1,"material":1,"matter":1,"may":1,"meaningful":1,"merkle":1,"multiple":1,"names":1,"new":1,"normalised":1,"note":1,"notes":2,"of":3,"off":1,"often":1,"on":6,"one":5,"or":4,"other":3,"over":1,"own":1,"page":11,"pages":3,"paragraph":2,"paragraphs":1,"parses":1,"passage":2,"pay":1,"plain":1,"point":4,"practice":1,"prefix":1,"punctuation":1,"queries":1,"quote":1,"quoted":1,"quoting":1,"rare":1,"re":1,"reader":1,"reasoning":1,"reference":1,"related":1,"rename":1,"reorder":1,"research":1,"resolve":1,"resolves":1,"revised":1,"rewrite":1,"row":1,"rule":1,"rules":1,"running":1,"s":5,"said":1,"same":2,"section":2,"see":3,"sentence":1,"share":1,"short":1,"situations":1,"slugified":1,"so":2,"something":1,"spaces":1,"specific":2,"stable":2,"still":1,"supports":1,"surgically":1,"surrounding":1,"t":1,"table":2,"takes":1,"target":1,"text":3,"than":1,"that":4,"the":19,"their":1,"them":3,"then":1,"thing":1,"this":2,"three":1,"time":1,"to":12,"transclusion":3,"tree":1,"try":1,"under":2,"unique":1,"updates":1,"use":2,"useful":1,"using":1,"vault":1,"via":1,"want":2,"wanted":1,"when":3,"where":1,"why":1,"wikilink":1,"wikilinks":1,"wins":2,"with":2,"within":2,"without":1,"writing":1,"you":7,"your":1,"zetl":4}},{"dl":380,"n":"Writing Pages","s":"writing/writing-pages","secs":[{"h":"Writing Pages","l":6,"t":"In a zetl vault, one page is one Markdown file. No databases, no sidecar XML, no magic — if you can write a `.md` file, you can write a zetl page."},{"h":"The filename is the title","l":11,"t":"zetl uses the filename (minus `.md`) as the page title. Title-case filenames with spaces are the convention: When another page writes `[[Zettelkasten Method]]`, zetl resolves the link by matching the filename. Slugification happens at *render* time — the URL becomes `/zettelkasten-method/`, but your filesystem stays human-readable. Case doesn't matter for resolution; `[[zettelkasten method]]` works too. There is no restriction on where a file lives. Folders are for your eyes, not zetl's. See [[Organising Your Vault]]."},{"h":"A minimal page","l":31,"t":"Every page is CommonMark-compatible Markdown with optional YAML frontmatter at the top: That's it. No build step, no registration. Save the file; it's a page. Run `zetl list` and you'll see it."},{"h":"Markdown flavour","l":60,"t":"zetl parses CommonMark plus a small set of widely-supported extensions: fenced code blocks, tables, footnotes, strikethrough, task lists. Wikilinks (`[[...]]` and `![[...]]`) and block anchors (`^id`) are zetl-specific — see [[Wikilinks]] and [[Headings and Blocks]]. If you have been using Obsidian or another wikilink editor, your existing Markdown will almost certainly parse without changes. See [[Migrating from Obsidian]]."},{"h":"zetl doesn't impose a method","l":71,"t":"You don't have to run a Zettelkasten. You don't have to run Johnny Decimal. You don't have to write atomic notes, daily notes, MOCs, or any other thing. A vault of long-form essays works. A vault of one-line bookmarks works. A vault that mixes research notes, a reading journal, and project plans works. The only rule zetl cares about: *pages are files, links are `[[links]]`*. Everything else is your call."},{"h":"Writing workflows","l":81,"t":"A few idioms that tend to work well: | Situation | What to do | |---|---| | New thought, not sure where it belongs | Create `Inbox Note 2026-04-18.md` at the vault root, link later | | Long-form draft | Keep it in one file until a section starts getting linked to — then extract | | Reference material | One page per concept; link liberally from narrative pages | | Journal | `Journal/2026-04-18.md` per day; links to whatever you were thinking about |"},{"h":"Previewing what you wrote","l":92,"t":"Three ways to see the rendered page: See [[Terminal Viewer]], [[Web Server]], and [[Static Site Export]]."},{"h":"Related","l":104,"t":"- [[Linking Pages]] - [[Organising Your Vault]] - [[Frontmatter]] - [[Your First Vault]] - [[Wikilinks]]"}],"tf":{"a":15,"about":2,"almost":1,"anchors":1,"and":7,"another":2,"any":1,"are":5,"as":1,"at":3,"atomic":1,"becomes":1,"been":1,"belongs":1,"block":1,"blocks":2,"bookmarks":1,"build":1,"but":1,"by":1,"call":1,"can":2,"cares":1,"case":2,"certainly":1,"changes":1,"code":1,"commonmark":2,"compatible":1,"concept":1,"convention":1,"create":1,"daily":1,"databases":1,"day":1,"decimal":1,"do":1,"doesn":2,"don":3,"draft":1,"editor":1,"else":1,"essays":1,"every":1,"everything":1,"existing":1,"export":1,"extensions":1,"extract":1,"eyes":1,"fenced":1,"few":1,"file":5,"filename":3,"filenames":1,"files":1,"filesystem":1,"first":1,"flavour":1,"folders":1,"footnotes":1,"for":2,"form":2,"from":2,"frontmatter":2,"getting":1,"happens":1,"have":4,"headings":1,"human":1,"idioms":1,"if":2,"impose":1,"in":2,"is":5,"it":5,"johnny":1,"journal":2,"keep":1,"later":1,"liberally":1,"line":1,"link":3,"linked":1,"linking":1,"links":2,"lists":1,"lives":1,"ll":1,"long":2,"magic":1,"markdown":4,"matching":1,"material":1,"matter":1,"method":1,"migrating":1,"minimal":1,"minus":1,"mixes":1,"mocs":1,"narrative":1,"new":1,"no":6,"not":2,"notes":3,"obsidian":2,"of":3,"on":1,"one":5,"only":1,"optional":1,"or":2,"organising":2,"other":1,"page":9,"pages":4,"parse":1,"parses":1,"per":2,"plans":1,"plus":1,"previewing":1,"project":1,"readable":1,"reading":1,"reference":1,"registration":1,"related":1,"render":1,"rendered":1,"research":1,"resolution":1,"resolves":1,"restriction":1,"root":1,"rule":1,"run":3,"s":3,"save":1,"section":1,"see":6,"server":1,"set":1,"sidecar":1,"site":1,"situation":1,"slugification":1,"small":1,"spaces":1,"specific":1,"starts":1,"static":1,"stays":1,"step":1,"strikethrough":1,"supported":1,"sure":1,"t":5,"tables":1,"task":1,"tend":1,"terminal":1,"that":3,"the":13,"then":1,"there":1,"thing":1,"thinking":1,"thought":1,"three":1,"time":1,"title":3,"to":8,"too":1,"top":1,"until":1,"url":1,"uses":1,"using":1,"vault":8,"viewer":1,"ways":1,"web":1,"well":1,"were":1,"what":2,"whatever":1,"when":1,"where":2,"widely":1,"wikilink":1,"wikilinks":3,"will":1,"with":2,"without":1,"work":1,"workflows":1,"works":4,"write":3,"writes":1,"writing":2,"wrote":1,"xml":1,"yaml":1,"you":9,"your":7,"zetl":9,"zettelkasten":1}}]}