From 1f1969ab281d8a466a237a622d5ac31a8a5f5df7 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Tue, 11 Aug 2026 09:07:38 +0200 Subject: [PATCH] vita: generate the project index, and add a web_visible axis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The project index was the last hand-maintained duplicate of CV data, and it had already drifted: a superseded Weenix line count and an end date the CV disagreed with. It now renders from yaml-source/data/projects.yml, which grows from three entries to nine and gains `group` (drives the index's sections, first-appearance order, like now.yaml) and `links`. Pmacs, Levshell, and Epiphany join under "Personal" — self-directed tools rather than coursework or employment — written from the repos rather than from memory. They are web-only: personal infrastructure is not academic output, and the résumé is a full page. LeVCS keeps its essay; project writeups stay essays, since a writeup is an informal presentation of a project and not a record of it. web_visible is a third visibility axis alongside cv_visible and resume_visible, defaulting to cv_visible so nothing changes until it is set. The file already knew how to let the CV and résumé disagree about an entry; generating a third surface from the same data would otherwise have collapsed that, and a document handed to a chosen reader is not a page that gets crawled. This is what makes web-only projects expressible — and what would let the xAI entry sit quietly on the CV while staying off the site, if that is wanted later. It stays off both for now. Two LaTeX conversions added while moving the markdown prose into the YAML: $\rightarrow$ and $\sim$. The latter fixes a bug I introduced doing it — "~10 crates" meant "about ten", but bare ~ is LaTeX's non-breaking space, so it rendered "( 10 crates" and dropped the approximation. Weenix's description now carries the site's fuller tail (the CS 169 origin and the later pipes/preemption work) rather than the CV's terser "Brown CS 169." Single-sourcing means picking one, and this is the better one; the CV grows by a clause and the résumé still fits on one page. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01SUGesXiMmACsLBTGG1xuEU --- build/Site.hs | 15 +++- build/Vita.hs | 133 +++++++++++++++++++++++++++++---- content/cv/projects.md | 20 +---- static/cv.pdf | Bin 42245 -> 42485 bytes static/resume.pdf | Bin 31010 -> 31195 bytes templates/projects.html | 7 ++ yaml-source/data/projects.yml | 137 ++++++++++++++++++++++++++++++++-- 7 files changed, 271 insertions(+), 41 deletions(-) create mode 100644 templates/projects.html diff --git a/build/Site.hs b/build/Site.hs index 6c54a7c..023bc38 100644 --- a/build/Site.hs +++ b/build/Site.hs @@ -29,7 +29,7 @@ import Compilers (essayCompiler, postCompiler, pageCompiler, poetryCompiler, fi import Catalog (musicCatalogCtx) import Commonplace (commonplaceCtx) import Now (nowCtx) -import Vita (vitaCtx) +import Vita (vitaCtx, projectsCtx) import Contexts (siteCtx, essayCtx, postCtx, pageCtx, poetryCtx, fictionCtx, compositionCtx, contentKindField, recentFirstByDisplay, tagLinksFieldExcludingTopSegment, isProvedConfidence) @@ -404,7 +404,18 @@ rules = do -- so nginx serves them via index-file resolution and the URLs stay stable -- if the underlying files are later reorganized into co-located directories. -- --------------------------------------------------------------------------- - match "content/cv/*.md" $ do + -- The project index is generated from yaml-source/data/projects.yml — + -- the same file that feeds the CV's and résumé's project sections — so + -- it gets projectsCtx and its own template. The markdown keeps only the + -- page's framing prose. Individual project writeups stay as essays. + match "content/cv/projects.md" $ do + route $ constRoute "cv/projects/index.html" + compile $ pageCompiler + >>= loadAndApplyTemplate "templates/projects.html" projectsCtx + >>= loadAndApplyTemplate "templates/default.html" projectsCtx + >>= relativizeUrls + + match ("content/cv/*.md" .&&. complement "content/cv/projects.md") $ do route $ customRoute $ \ident -> let fname = takeFileName (toFilePath ident) slug = takeWhile (/= '.') fname diff --git a/build/Vita.hs b/build/Vita.hs index 18653e3..f7fb4eb 100644 --- a/build/Vita.hs +++ b/build/Vita.hs @@ -21,10 +21,12 @@ -- for the full supported set and why escaping happens before conversion. module Vita ( vitaCtx + , projectsCtx ) where import Data.Aeson (FromJSON (..), Object, Value (..), withObject, (.:), (.:?), (.!=)) import Data.Aeson.Types (Parser, typeMismatch) +import Data.Char (toLower) import Data.List (isPrefixOf, sortOn) import Data.Maybe (mapMaybe) import Data.Scientific (isInteger, toRealFloat) @@ -62,6 +64,23 @@ reqLoose o k = unLoose <$> o .: K.fromString k optLoose :: Object -> String -> Parser (Maybe String) optLoose o k = fmap unLoose <$> o .:? K.fromString k +-- | Whether an entry appears on this page. +-- +-- The YAML has carried two visibility axes since it drove two documents: +-- @cv_visible@ and @resume_visible@, so the CV and the résumé can disagree +-- about an entry without duplicating it. Generating the vita page from the +-- same data added a third surface, and folding it into @cv_visible@ would +-- have silently collapsed a distinction the file already knew how to make +-- — an entry can be worth keeping on a document handed to a reader while +-- being wrong for a page that is crawled. +-- +-- @web_visible@ therefore defaults to @cv_visible@: existing entries behave +-- exactly as before, and the axis only exists where someone sets it. +webVisible :: Object -> Parser Bool +webVisible o = do + cv <- o .:? "cv_visible" .!= True + o .:? "web_visible" .!= cv + -- --------------------------------------------------------------------------- -- Entry types -- --------------------------------------------------------------------------- @@ -84,7 +103,7 @@ data Edu = Edu , edEnd :: Maybe String , edGpa :: Maybe String , edNotes :: Maybe String - , edVisible :: Bool + , edWeb :: Bool } instance FromJSON Edu where @@ -96,7 +115,7 @@ instance FromJSON Edu where <*> optLoose o "end" <*> optLoose o "gpa" <*> optLoose o "notes_cv" - <*> o .:? "cv_visible" .!= True + <*> webVisible o newtype EduDoc = EduDoc { unEduDoc :: [Edu] } @@ -112,7 +131,7 @@ data Pub = Pub , pbTarget :: Maybe String , pbLinks :: [Link] , pbNote :: Maybe String - , pbVisible :: Bool + , pbWeb :: Bool } instance FromJSON Pub where @@ -125,7 +144,7 @@ instance FromJSON Pub where <*> optLoose o "target" <*> o .:? "links" .!= [] <*> optLoose o "equal_contrib_note" - <*> o .:? "cv_visible" .!= True + <*> webVisible o newtype PubDoc = PubDoc { unPubDoc :: [Pub] } @@ -140,7 +159,7 @@ data Pres = Pres , prYear :: String , prMonth :: Maybe String , prStatus :: Maybe String - , prVisible :: Bool + , prWeb :: Bool } instance FromJSON Pres where @@ -152,7 +171,7 @@ instance FromJSON Pres where <*> reqLoose o "year" <*> optLoose o "month" <*> optLoose o "status" - <*> o .:? "cv_visible" .!= True + <*> webVisible o newtype PresDoc = PresDoc { unPresDoc :: [Pres] } @@ -170,7 +189,7 @@ data Exp = Exp , exOrder :: Int , exPreamble :: Maybe String , exBullets :: [String] - , exVisible :: Bool + , exWeb :: Bool } instance FromJSON Exp where @@ -185,27 +204,54 @@ instance FromJSON Exp where <*> o .:? "cv_order" .!= 99 <*> optLoose o "cv_preamble" <*> o .:? "bullets" .!= [] - <*> o .:? "cv_visible" .!= True + <*> webVisible o newtype ExpDoc = ExpDoc { unExpDoc :: [Exp] } instance FromJSON ExpDoc where parseJSON = withObject "ExpDoc" $ \o -> ExpDoc <$> o .: "experience" +data Proj = Proj + { pjName :: String + , pjGroup :: String + , pjEssay :: Maybe String + , pjStart :: String + , pjEnd :: Maybe String + , pjDescription :: String + , pjLinks :: [Link] + , pjWeb :: Bool + } + +instance FromJSON Proj where + parseJSON = withObject "Proj" $ \o -> Proj + <$> reqLoose o "name" + <*> o .:? "group" .!= "Projects" + <*> optLoose o "essay" + <*> reqLoose o "start" + <*> optLoose o "end" + <*> reqLoose o "description" + <*> o .:? "links" .!= [] + <*> webVisible o + +newtype ProjDoc = ProjDoc { unProjDoc :: [Proj] } + +instance FromJSON ProjDoc where + parseJSON = withObject "ProjDoc" $ \o -> ProjDoc <$> o .: "projects" + -- | @personal.yml@ also carries a @display@ string per link (the value the -- CV prints in full, since paper cannot be clicked). It is deliberately -- not read here — see 'renderContact'. data ProfileLink = ProfileLink { plLabel :: String , plHref :: String - , plVisible :: Bool + , plWeb :: Bool } instance FromJSON ProfileLink where parseJSON = withObject "ProfileLink" $ \o -> ProfileLink <$> reqLoose o "label" <*> reqLoose o "href" - <*> o .:? "cv_visible" .!= True + <*> webVisible o -- | Contact details from @personal.yml@. The phone number is deliberately -- not parsed: it is printed on the CV PDF, which is a document handed to @@ -246,6 +292,10 @@ latexToHtml = . substAll "--" "–" . substAll "$\\times$" "×" . substAll "$\\delta$" "δ" + . substAll "$\\rightarrow$" "→" + -- Approximation, not a non-breaking space. Bare `~` is LaTeX's nbsp, so + -- "~10 crates" silently renders as "( 10 crates" and loses the "about". + . substAll "$\\sim$" "~" . substAll "\\#" "#" . substAll "{,}" "," . substAll "~" " " @@ -371,7 +421,7 @@ renderEducation es , "" ] where - visible = filter edVisible es + visible = filter edWeb es one e = concat [ "
  • " , "
    " @@ -395,7 +445,7 @@ renderPublications ps , footnote ] where - visible = filter pbVisible ps + visible = filter pbWeb ps -- The dagger legend lives on whichever entry declares it, but reads as a -- section-level note, so it is rendered once at the foot of the list. footnote = case mapMaybe pbNote visible of @@ -434,7 +484,7 @@ renderPresentations ps , "" ] where - visible = filter prVisible ps + visible = filter prWeb ps dateOf p = maybe "" (\m -> tex m ++ " ") (prMonth p) ++ tex (prYear p) one p = concat [ "
  • " @@ -458,7 +508,7 @@ renderPresentations ps renderExperience :: [Exp] -> String renderExperience xs = research ++ industry where - visible = sortOn exOrder (filter exVisible xs) + visible = sortOn exOrder (filter exWeb xs) isRes e = exSection e == Just "research" research = group "experience-research" "Research Experience" (filter isRes visible) industry = group "experience-industry" "Industry Experience" (filter (not . isRes) visible) @@ -481,13 +531,57 @@ renderExperience xs = research ++ industry , "
  • " ] +-- | The @/cv/projects/@ index. Groups render in first-appearance order, the +-- same convention "Now" uses for its sections — reordering the YAML +-- reorders the page and no separate ordering key is needed. +-- +-- Entry titles link to the project's essay where one exists. The essays are +-- deliberately not generated: a writeup is an informal presentation of a +-- project, not a record of it, and belongs in the same voice as the rest of +-- the essays. +renderProjects :: [Proj] -> String +renderProjects ps = concatMap one (groupOrder visible) + where + visible = filter pjWeb ps + groupOrder = foldl (\acc g -> if g `elem` acc then acc else acc ++ [g]) [] + . map pjGroup + -- "Machine Learning & Deployed" → "machine-learning-deployed". + slugify s = case foldr step [] s of + ('-':rest) -> rest + cleaned -> cleaned + where + step c acc + | c `elem` (['a'..'z'] ++ ['0'..'9']) = c : acc + | c `elem` ['A'..'Z'] = toLower c : acc + | null acc || head acc == '-' = acc + | otherwise = '-' : acc + one g = section ("projects-" ++ slugify g) (escapeHtml g) $ concat + [ "" + ] + entry p = concat + [ "
  • " + , "
    " + , "

    " + , case pjEssay p of + Just u -> "" ++ tex (pjName p) ++ "" + Nothing -> tex (pjName p) + , "

    " + , metaLine (dateRange (pjStart p) (pjEnd p)) Nothing Nothing + , "

    ", tex (pjDescription p), "

    " + , renderLinks (pjLinks p) + , "
    " + , "
  • " + ] + renderContact :: Person -> String renderContact p = section "contact" "Contact" $ concat [ "

    " , "" , escapeHtml (pnEmail p) , "" - , concatMap one (filter plVisible (pnLinks p)) + , concatMap one (filter plWeb (pnLinks p)) , "

    " ] where @@ -541,3 +635,12 @@ vitaCtx = <> sectionField "vita-contact-html" (renderContact <$> loadYaml "yaml-source/data/personal.yml") <> siteCtx + +-- | The @/cv/projects/@ index. Reuses the vita flag so it picks up the same +-- stylesheets and reads as the same kind of surface. +projectsCtx :: Context String +projectsCtx = + constField "vita" "true" + <> sectionField "vita-projects-html" + (renderProjects . unProjDoc <$> loadYaml "yaml-source/data/projects.yml") + <> siteCtx diff --git a/content/cv/projects.md b/content/cv/projects.md index 9e6f395..524054b 100644 --- a/content/cv/projects.md +++ b/content/cv/projects.md @@ -4,22 +4,6 @@ tags: meta portal: true --- -Index of engineering artifacts. Systems depth is the primary axis of this page; machine-learning and deployed artifacts follow. +Index of engineering artifacts, generated from the same data as the project sections of the [CV](/cv.pdf) and [résumé](/resume.pdf). Systems depth is the primary axis; self-directed tools and deployed machine-learning work follow. -## Low-Level & Systems - -- **[Weenix](/essays/weenix/)**\ - Unix-like kernel in 10,000 lines of C. Virtual memory, VFS, system calls, threading, device drivers, interrupt handlers, and file systems; custom linker support for running userspace x86-64 ELF binaries. Originally a project from Brown CS 169 (Operating Systems with Lab), extended with further features like pipes and userspace preemption. January – August 2025. -- **[Networking Stack from Scratch](/essays/networking-stack/)**\ - TCP/IP, RIP, UDP, and DNS in Go, supporting file transmission of up to 1 GB across networks of 8 virtual machines. Extended with a fully RFC-compliant SSH implementation (2,000+ additional lines) supporting sustained sessions of arbitrary length. October 2024 – July 2025. -- **[Where Does SIMD Help Post-Quantum Cryptography?](/essays/where-does-simd-help-post-quantum-cryptography/)** · [Artifact](https://git.levineuwirth.org/neuwirth/where-simd-helps)\ - Hand-written AVX2 assembly for ML-KEM / Kyber. 35×–56× speedup over compiler-optimized C for core NTT arithmetic; 5.4×–7.1× end-to-end KEM speedup. Full statistical-analysis pipeline (Mann-Whitney U, Cliff's δ, bootstrapped CIs) on Brown's OSCAR HPC cluster. Phase 1 report and reproducible artifact public. -- **[LeVCS](/essays/levcs/)** · [Artifact](https://git.levineuwirth.org/neuwirth/levcs) · [Instance](https://levcs.levineuwirth.org)\ - Distributed version control system in Rust (~10 crates, 194 passing tests at v0.1.0). BLAKE3 content addressing, signed Ed25519 authority chains for protocol-level identity and push authorization, federation as the normal operating mode with three storage modes (full / release / metadata), and a cascading per-file merge engine (textual → format-aware → tree-sitter → wasm plugin) that resolves git's common false conflicts. Substrate complete; workflow surface (PR/review, issues, web UI) deferred. First federation instance at [levcs.levineuwirth.org](https://levcs.levineuwirth.org). April 2026 – present. - -## Machine Learning & Deployed - -- **[ICD-10-CM outcome calculator](https://levineuwirth.github.io/icd_embeddings/)** · [Preprint](/essays/beyond-comorbidity-indices/) · [Code](https://github.com/levineuwirth/icd_embeddings)\ - Public, read-only calculator for the permutation-invariant Deep Sets model underlying the paper currently under review at *JAMIA*. Takes a diagnosis-code set; returns 30-day readmission and postdischarge mortality predictions with Integrated-Gradients attribution. -- **[NeuroPose](/essays/neuropose/)**\ - 3D pose-estimation and kinematic-analysis system for neurological-recovery research in Liqi Shu's laboratory at Brown Neurology. Python/TensorFlow inference pipeline, MATLAB-based statistical post-processing, Rust backend with HTML/JS frontends. 20,000+ lines across four externally-funded sub-projects since 2023. +Where a project has a writeup, its title links to it. Those are essays rather than records — informal, longer, and written in their own voice. diff --git a/static/cv.pdf b/static/cv.pdf index 920a6e0b1d4b50d7e67fe770b0b3967b4338de1e..28d2e99e2d48c61ca2bfa749e3108f16fce4a640 100644 GIT binary patch delta 6447 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 HRR-ff8|3g` diff --git a/static/resume.pdf b/static/resume.pdf index 3f7e533fb410a5ca08c24968002898be65e463a3..f948eb9727d7bff25c36a8696f6235dc34e67880 100644 GIT binary patch delta 6417 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∓9B3SXJ&(!vCiU-lu#uM z!4ji`q{b9K2H_~37B;;N1^%5Os7r?P)9|sZ#!2-F6={&lX{FwV>6#mIJ4rAt4!ps>9};c)WI~ymk>W-bMNgg~sK4nOj-xlG zjh9iNAk~n~iJcmD-?T`2^?G?W`{_f--vxK-r3(}kGjBcCv6MDqNd8M9BH?GLV4b;+ z*UTFrAA3mLMSJAhReqGbcFZa}VESOWCVHE1B-<-)_&?UipK3GIT>aH~82xkeCAlnB z0-WbL5*aNkk7;uICDX0w@i*~1nXRdBPR?G|5#zNp725Mr=YmHUU8Z@c9g5eIi#18xRjp-ye9R?{tpdU**D*r%d?_or6D^B+XHQ}H|_9pou%rZ zC~)C}w6aUaKHS6_%Cb3y+Eg*YIivIi)2N6TC$#obbGf6tO6~QiNl4s{a5!d3 za&|6D|BrW~#99Wtd7BKV9o1QywtR=|b|()aJgVBUll1VTCIvpQ+;GGQ70G!^Sv=xP z-W0r)GfM}4w@vl!vK*E9qoWh$^BYmRzuy+JMij619%ri>NAVM)?88%hbuW^d5R6#N ze*V~J9XaG#<@U;Ml{Ln{X+JtHJafokyPt%VSCdtbKa&rVYjgm}J&Yum^a5ama5}f4)BHk_A#%dsS^sZ?TbIr z#$p^BUUHYG0J%t42*8M$@XZllQ5UHC^w>AtTmNg|8~g>E;%l~k(Dm@q#rIbrdnWsp zu-)U&)Ad9Dp!@-RY5flG!n@@+iQnOy_?6*;2HTuu;RJfwKfgN>kltgN-=o)P%OEs; zc81*Xju9AbH=L60!y<0M!DGe6%WH0C$!*DH4L0L9w=mHatyB6=i d|H0(`Vox9voTfbcQlRs5@}twyNvTMq{|^t|Wc~mE delta 6232 zcmV-e7^mmk^8upr0gyF+GdVRdK0XR_baG{3Z3=kW?OZvJEVmKP{VOK4gKhCBl1*SS zfdR{aGr=#j9qi2XIx89 z)nWknwlV#sYZ_DD;}W1Qq5L+zFVy=g<sXrR9&JkV)zt{l1D@b&!Wlq%PKv>ao6(uLm3=D#Hm z6V;+a+zI6Id9e6oR^7sFIO{A%T7xTVFpi7}9RBw3+BONrDf9B0(k+&_x?xQh5>}Ft z{VW1B@c3HZEY^qD*WiZT=v~JqZfaA({YJl zd28w1ko4}Ly!UiHe2>nH_c0gz%-8?rv{;nTzxjG5T`#%A&nH-;`24TaUr#@kRph;Z zSF2bBxWyRW9o|9T(jBiTSX6x3E$$ALUm^&5n7-l~*~7dk=i2pbid4>;14{^e&Y(6F zxLA`;GVuX_<7HltHkytK-BET?!Sg{1E@dewGGCVy6u(QB99T^`RAtX979DI=*J2%N z3|}U;K+|8tepNSGh&FX`F3Htgi)_%rP}Y{CK#5ATE0pM# zudvq?#c`=Fpk14d#qV!LIoW*KlkypSL=tifQrB{SEnFh(;?JSqyy+yRrEAuG=X|es7vj$u}zQLr2-_JeIZAI}W;H{??Y)ax+|R$q&#;0`~T9 z$==I<>TJm}xH4RN6GS>9pIzjLK~CC3UCl<{sKIcvQ?Ox{^;V;YW|6ozJSWG`Y_e8l zYIIq?OFnYr{a10DyiYVJmF&$VP-2G&jbd<}j!JyaOVTyfT5$_9Di<*QK1>hQ%(c=> zI^zp=-k<4JORqCEyH3rnWE(c=$cms}San-}Q9h7r-FtH+4ij!?$$S+ObJmi#4_qx) z4`9GDT$zvH(1J40Mqfi!H6rpsHZMHWb0Bk^Rm8sXomf^aDCAJVkue0?90gsp=8~aw zITwFqr!dzlSuI>rnrzt04f1WtlzOF*_(Wi-BCutX%{%a04CGm;*%*(7F}3^RQsVM| zTOY`qi`Q>uu%Yk*w5dLARIRn7N{h)dqW}|0$n5$+K1IGX;*Yd?msXE%;D9(CZ` zYS%$Hx`~`Wr!eXIcy4U7#I!cZnzW>xnhHUrLe#Sp((txLhZcs-XWKX18>&jbkNch* zLBetxL>_Z1E<-qF*O#75z)~{qHc_Qk`H9b)xx~r$y=}EW(6-YyWFi3ze zT4<2|j+Z1DVflSAz?r8YH2|(&a02wO=uhrk*XHCUO9CbQ(t!myiueaM-r=Xo`8g}- zb|%TO5C%b@H7ipveuKY4Nz9rNrzNCGL3*C3H!0YC=;<{|_*6IS-7HrIOiGL)ROfyO zSZiiTslYA};*ik{EfN?3d{hK~Vp}+4kj<#4{U%k*K(~Top%Rl!!WPgAC4*HGh7n^Z zP5|QG-nRT>!wzQiO3AHjuGGDAw7P8@aJExlO4X&}De2HhN}rhih3zzpmX;{(Xzta` zM}$x49x|G1Q#;%39)cj$1xEPwzEfOdsGu;Esvw*zlUemlrHiQ28dCXxBtcw?Fi@Ad zayQLQgd>PLfGsR*IaD)j@G{S!7PQARpe4U+1Np^i)f=Sa9tY4T5gMMTdZq z{b1dfX-!tVK9ugLLU4VdosWeP#!8Gih6@gqj1J{7ec+UW;Lw=AB@gQmJUj7VDV2}u z@Ez}HL~FA(yi)BMt(S^_B3?%r^oU|h8v2E=gXAlbs&Q0(~Iic#}&StSNPXSq^p5J z7E|@cENH7*m^i4WeJQi<3K?5B4R_xY@<%>4lRb@A;PS2n4M}ZR)~c)6M?fPv)fzr` z?A5GPui;rD}}OhZdTIpu`kNiqAK}b%pXV(DH+|0l8iG&mv8!F{+Rb94R?(` z%-^bmgH%&biuM#TQ^6Hl2?T|-$Sm=tgj9WHsp;Q{`pQI>?`F?$iESF zbl4BZVSu22tKXSE-qjL%>u=jq4XPy0Ria5HLnEU?;jPb%lAZH<+AemmL~UQQ85_=o z@QRJdTZ7$^mR*mVV`1*NzGy90`NabXR@z0ugm|~-?Ba33K{re(Wlo5UnR>l zbY5gu#ts6$^L6&S~OHG-7>%FFB_`Ju*a0tw~VH-P3j1%Av zYUW(Y;WB4gdjxewC0FaGjM26^>Jt7dDiJS^*jGq=1lvo+M|$2U9ojU4L)w>GES%#$ zwRi9LTSgty*9|9^ZZYK<+R{b>5n7VLg|S^P=y1m3%jJk^Z1B z`&}b{{*0>Sq9LOfB}5n>4Dn4OL~toWcrk*+$+HBzI@VH2B}sU(YxX7JV|s|nfKE5E z$qKu!zXa=j2k1$nple#*r&M-G=T|pC_ia~!^hC*BnLWSFibvrP-85Nph*UbX8Ln(! z>g=e;1vF5W3$i4MLSY^bk%4mQ%BD?`@fVMO#>mo(F?9)ji>C6#mlp7(mK$xFSE;#I ze~$06T|ai}DYl1$i3m560jYe_h2~Lt*e#iEOn%| zJs2XrlzA0yZwcDl2}o8gy;ek=RjZ|wzI;zEV`|1}mJA>^87m*pJalE=bYeHt-(zQ0 z(+m(`6|)0!)A5nMkZTTNJD#V@TKyQWjHVL~%=REJU&z19r{%UfiNNQm0T;!dZ#Ua< zPzl=@FzyRnu6{oBVy=6CJXhOxbwY3UAX5Vf4@Ow-ql->bHAn4gbsqDOlB$H-RZ66l zbLBC;^qR0mU4$WKARNvR@y$74iAN8{5g%g*qP^>`x7Qgf@qm;S6*Dnd z2TxuY>dGhqhAAl3`FzZX!Rs|6XfhzCfX%nPa3T-Vc^!vXGpu{^XGa(K%4_4+%V;>9o8NA6ax6&@#|1#l;KM7TvF1(28 zD0XT|em$6!>gp;>)P5hY?@pg_&^2~l3`*)C=+-E+k5F=kfxP&MA(@^W=n5pN>Z@PL z?V0{L31W-qErXhWLO0dEheiaV>hY+p#Zvr@I?_hKJ);1J_UTe_Kz5~kcSnKp+V1d*TR-e`^zkKFaOhgsL;^xhAT zSvuE4jr$Kq%sL!$Y}L6t4{dgVy}Fp}fA|8;D{&?tK;NV*!&=59LJ)WnYS}w9r}jqG z0t$qud*v>!i(9oESFhct?=Y7!vEEfJ;pHI-!_{M1V}9NGr&zMN#`h;$zDHT^dw9T0 z)|&*jIL*R;*TlUKHKle(MP(r$|K}d*Ioz#r$<)OE2LYi6rGhrp$`gudToJt3N)27)l+O6?~I}wOMGih{y&Yph2-k%N@l^-syOwh z-~9)05F4bkX%IwU0W*`Xa24R=ko^dAduRIfiNvD~ROiP73%vj5WXAWH{nRggBEc3X+Fqe)= zSaXAG*l{nJBqy9R3=56h4$dTJ5{<(Ut)iI1B<*12sI^SvzQ$y2n6Q8Fq%$VDHH`c! zBbb4EEU0kXWsYg4xCSVu1Wm$#vYc6_EN#QUEk-qlGeW81-oY{EDeMpKwDy34WgI+; zjA`#2Ggyvuti)mBac!|YCcSu(UfwQ?^l-7L+Vo=ev8Aim<>Ei-QB_ZiI_KbzkJ6L$ zG#%gqdl{vNn0ArAe|LXMpWe;5oVV5U`QmT$;&ZtuR)3Urd!1DE?BM0g^j$G&bD?>n z4K^iR;y|htCgGq45EIAM=;h0wz6X-)wp}*Q)AT_cGV=-1w8o)~OKkSTaLmd!xr-%T zUu-}rYZGhb7F4DaPO|)ApsrURlSy^6$I}Lm<}%5=*}%~ zz@&V#3(1MZX!(DUPe)f-rR8M$@8afTF$LOLlWxTg5#eAnZC#Ru9KsVpXVi-%)3!r* zTrFB^a6kd%U3#o=rbEZjDGohH=Qi{_o!ro)WI;Ww_O`Aj7e$+=Z->X}W$~p=PjAMv zVi237*qnCj{bPT8Q#5&RSWWsD?YM3akS*Y*w}ee14rEx8Z1ZqG{q?sGND&F*jNGtT z&F7=t5uMRUrV_s%#1g}zX&XwJ0p3r!rw%3C(1_T`*r?b9lQ8*R(NwE?QZ$r$X_*kd zw?K!lx>&T7UFrS+#No2ZW$-wPA#Nxa$931Cr@0NxSD1e*(z9~f#0`wna|mpFRDB5= zuz=(;+0itpi*Z}kz1QQ*;#c+ug3Yestgees2SoUEH7V-em+9wnIsJ6=h5g=>Tv{%K z`u*S_&MvFPaNHKX;qx%$Q6apR#ysQlXU=~gTq2+wr`b5#!D|gh^|SZ|QR<7$TTAKRj;zkD z5F=}Is#Wx%yH19?E}DA*mwx0m%^GEvZZhRFMwh6BWQ%&F(9gmc%>@@Zxe~(A>|ndW z!w8(xd4}i(B%BjO>S83oz~Y=IVTm1QBcJ7j$5>6VxghI#-#0*=c{&pkCMn+)}Ff-$GZ8KoBx%L)q=NLsEoeUk} zawUH$QedA<(Q&yESd{Da9qn@?4LXRwQF142n<9*U_+@zY&y#1!vcKHc#Ys_DP5-ExPs#k^wrPKh zo72UoDinrU*|ha-?{HduEDq8)RJ~Dgd#6*BfwH|lz)H*Iytttv!BH2Sa(IOj!P6^x{@yPX`}XN!Ga*mTYpQ6W-yBJ{5-oV zrzjN0xZvxEN=qNl)2y#JYPd3Z@D_g{C_P1{E+>bJ*}PypJ)Vzel-Kd}%i;ZYUI5YC z@uF&q7kvHa`QG2lZhur09_ElNj>~zWnC7u)g4CkLv)ese-vsI#Bh+6L-#X)%!&W>w?MbF%K#y*H>3-FqcJ*lGukdR`Y*x{rx4Q z)g8&0Zx#<`=DCPBWih+!X3rvJ;m53 zDCV&ho~jymS2c=1T{S0XSC_*NcL4pEETi2W$zKzaS2Tt{K{OA#f2v}{SBXZ6r%PsdG<F7r=`r?l7Yo;$ury9rqQl@!~!>wkF+|!JHf@U6B z;K{1_I??=Tkrcbuu~Y<}8~Uv;zRT53JGdU#Os22Lo9+hx*@yG9!P$Sa!TH5+f`287 zKPfib7+WeOQ3iY?2!1D)I^nt#i00e4ZsmY-+|MkhgsH(wo)1 zEthi$dlT%vy?irkc8@i8hc%(Mw4uM@?fA{BSA$Eeb$)seZ}u<9LqtFXaf1tZ152tr1BsD2p|Qs?H8L~sCWan>6#uSCUVRgE zjfgajCAK(ZLPlgk0uqym2&$(D3&bKV(k3ZM$d;^0hwMlOadg`yePWXy8IUmWN%_>3UhQ}a&&ldWo8O9Fg7zY3MC~)Peuy- C_9$!s diff --git a/templates/projects.html b/templates/projects.html new file mode 100644 index 0000000..bf1ed48 --- /dev/null +++ b/templates/projects.html @@ -0,0 +1,7 @@ +
    +

    $title$

    + +
    $body$
    + + $vita-projects-html$ +
    diff --git a/yaml-source/data/projects.yml b/yaml-source/data/projects.yml index 841cda5..f7bdc5e 100644 --- a/yaml-source/data/projects.yml +++ b/yaml-source/data/projects.yml @@ -1,29 +1,154 @@ -# Projects shown on CV under "Systems and Engineering Projects" and on -# résumé under "Projects". The PQC entry is research-grade and also -# appears in Publications on the CV; on the résumé it's the strongest -# project and leads the list. +# Projects. One list, three consumers, three visibility axes: +# +# cv_visible — CV, under "Systems and Engineering Projects" +# resume_visible — résumé, under "Projects" (ordered by resume_order) +# web_visible — the /cv/projects/ index (defaults to cv_visible) +# +# The web index was hand-written markdown until August 2026 and had already +# drifted from this file (a superseded Weenix line count, dates that ended +# where the CV said "Present"). It is now generated from here, so the three +# surfaces cannot disagree. Individual project *writeups* stay as essays — +# they are informal presentations, not records, and `essay` points at them. +# +# `group` drives the index's section headings and renders in first-appearance +# order, like now.yaml's sections. Descriptions are LaTeX-flavoured because +# xelatex is the first consumer; build/Vita.hs converts the same subset for +# the web (\textbf, \textit, \texttt, \href, $\times$, --, ~, {,}). projects: + # --------------------------------------------------------------------- + # Systems & Infrastructure + # --------------------------------------------------------------------- - name: Post-Quantum Cryptography on x86 AVX2 + group: Systems & Infrastructure + essay: /essays/where-does-simd-help-post-quantum-cryptography/ start: March 2025 end: Present description: "Micro-architectural study of SIMD contributions to ML-KEM / Kyber on Intel AVX2, conducted on Brown's OSCAR HPC cluster. Hand-written AVX2 assembly achieves 35--56$\\times$ speedup over compiler-optimized C for core NTT arithmetic; 5.4--7.1$\\times$ end-to-end KEM speedup. Full statistical analysis (Mann-Whitney U, Cliff's $\\delta$, bootstrapped CIs). Technical report and reproducible artifact public." + links: + - label: Report + href: /essays/where-does-simd-help-post-quantum-cryptography/ + - label: Artifact + href: https://git.levineuwirth.org/neuwirth/where-simd-helps cv_visible: false # appears under Publications instead on CV resume_visible: true resume_order: 1 + web_visible: true + + - name: LeVCS + group: Systems & Infrastructure + essay: /essays/levcs/ + start: April 2026 + end: Present + description: "Distributed version control system in Rust ($\\sim$10 crates, 194 passing tests at v0.1.0). BLAKE3 content addressing, signed Ed25519 authority chains for protocol-level identity and push authorization, federation as the normal operating mode with three storage modes (full / release / metadata), and a cascading per-file merge engine (textual $\\rightarrow$ format-aware $\\rightarrow$ tree-sitter $\\rightarrow$ wasm plugin) that resolves git's common false conflicts. Substrate complete; workflow surface (PR/review, issues, web UI) deferred." + links: + - label: Writeup + href: /essays/levcs/ + - label: Artifact + href: https://git.levineuwirth.org/neuwirth/levcs + - label: Instance + href: https://levcs.levineuwirth.org + cv_visible: false + resume_visible: false + web_visible: true - name: Weenix + group: Systems & Infrastructure + essay: /essays/weenix/ start: January 2025 end: August 2025 - description: "Full Unix-like kernel in 10{,}000 lines of C: virtual memory, VFS, system calls, threading, device drivers and interrupt handlers, and file systems with custom linker support for running userspace x86-64 ELF binaries. Brown CS 169." + description: "Full Unix-like kernel in 10{,}000 lines of C: virtual memory, VFS, system calls, threading, device drivers and interrupt handlers, and file systems with custom linker support for running userspace x86-64 ELF binaries. Originally Brown CS 169 (Operating Systems with Lab), extended afterward with pipes and userspace preemption." + links: + - label: Writeup + href: /essays/weenix/ cv_visible: true resume_visible: true resume_order: 2 + web_visible: true - name: Networking Stack from Scratch + group: Systems & Infrastructure + essay: /essays/networking-stack/ start: October 2024 end: July 2025 - description: "TCP/IP, RIP, UDP, and DNS implementations in Go, supporting file transmission of up to 1~GB across networks of up to 8 virtual machines." + description: "TCP/IP, RIP, UDP, and DNS implementations in Go, supporting file transmission of up to 1~GB across networks of up to 8 virtual machines. Extended with a fully RFC-compliant SSH implementation (2{,}000+ additional lines) supporting sustained sessions of arbitrary length." + links: + - label: Writeup + href: /essays/networking-stack/ cv_visible: true resume_visible: true resume_order: 3 + web_visible: true + + # --------------------------------------------------------------------- + # Personal — self-directed tools, not coursework or employment. Web-only: + # they are infrastructure I use rather than academic output, and the + # résumé is a full page already. + # --------------------------------------------------------------------- + - name: Pmacs + group: Personal + start: "2025" + end: Present + description: "Parallel Emacs: a Rust-cored, Lua-scripted editor in the Emacs tradition, at v1.1.0 with prebuilt binaries. The hot path (rope, buffers, views, async runtime, process supervision) is Rust; commands, keymaps, hooks, and packages are an embedded Lua VM. Partitioned into a long-lived instance and thin frontends over a typed protocol: a \\texttt{crossterm} TUI attachable over SSH with \\texttt{mosh}-style reconnect, and a GPU frontend (wgpu / winit / glyphon) that renders from a semantic projection of editor state and edits optimistically against a local CRDT replica. Buffers are optionally CRDT-backed, so both frontends can edit the same buffer concurrently." + links: + - label: Source + href: https://github.com/levineuwirth/pmacs + cv_visible: false + resume_visible: false + web_visible: true + + - name: Levshell + group: Personal + start: "2026" + end: Present + description: "Wayland-targeted productivity and research shell, in daily use as my main environment. Five Rust crates over a sway/i3-ipc substrate: a capture-and-restore planner for desktop state, an event-logged store with UUIDv7 ids, a daemon whose every action is a persisted job under a global lock with rollback capsules taken before any mutating restore, and a control CLI. Currently growing from a single machine to a fabric of machines that work as one, with project resume as the hero verb." + cv_visible: false + resume_visible: false + web_visible: true + + - name: Epiphany + group: Personal + start: "2026" + end: Present + description: "FOSS music-notation platform: a specified, deterministic, CRDT-based score model whose LaTeX specification suite is the source of truth rather than its documentation. Twelve Rust crates across two tracks — the wire format, bundle, operations, and text projection on one; the editing seam, engraving, and layout IR on the other. Work is scoped into ratified contracts with numbered pins, mutation tables, and adversarial review rounds before any code is dispatched." + links: + - label: Source + href: https://github.com/levineuwirth/epiphany + cv_visible: false + resume_visible: false + web_visible: true + + # --------------------------------------------------------------------- + # Machine Learning & Deployed + # --------------------------------------------------------------------- + - name: ICD-10-CM outcome calculator + group: Machine Learning & Deployed + essay: /essays/beyond-comorbidity-indices/ + start: "2025" + end: Present + description: "Public, read-only calculator for the permutation-invariant Deep Sets model underlying the paper under review at \\textit{JAMIA}. Takes a diagnosis-code set; returns 30-day readmission and postdischarge mortality predictions with Integrated-Gradients attribution." + links: + - label: Calculator + href: https://levineuwirth.github.io/icd_embeddings/ + - label: Preprint + href: /essays/beyond-comorbidity-indices/ + - label: Code + href: https://git.levineuwirth.org/neuwirth/beyond_comorbidity_indices + cv_visible: false # appears under Publications and the Shu Lab bullet on CV + resume_visible: false + web_visible: true + + - name: NeuroPose + group: Machine Learning & Deployed + essay: /essays/neuropose/ + start: "2023" + end: Present + description: "3D pose-estimation and kinematic-analysis system for neurological-recovery research in Liqi Shu's laboratory at Brown Neurology. Python/TensorFlow inference pipeline, MATLAB statistical post-processing, Rust backend with HTML/JS frontends. 20{,}000+ lines across four externally-funded sub-projects since 2023." + links: + - label: Writeup + href: /essays/neuropose/ + - label: Artifact + href: https://git.levineuwirth.org/neuwirth/neuropose + cv_visible: false # appears via the Shu Lab bullet on CV + resume_visible: false + web_visible: true