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 920a6e0..28d2e99 100644 Binary files a/static/cv.pdf and b/static/cv.pdf differ diff --git a/static/resume.pdf b/static/resume.pdf index 3f7e533..f948eb9 100644 Binary files a/static/resume.pdf and b/static/resume.pdf differ 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