Skip to content

Repository files navigation

Healthcare Data Tools

The landing page for coffeemilktea.github.io — a home for browser-based, fully client-side tools for DICOM and HL7 medical data standards.

Six of the seven tools live in hl7-dicom-tools and are served under /hl7-dicom-tools/. The seventh, the Mirth Transformer Builder, lives here in tools/. This repo also holds the landing page, the standalone reference pages, and the shared theme controller.

No build step, no framework, no CDN — htmx is vendored and the type is a system font stack, so a page makes no third-party request at all. GitHub Pages serves plain files straight from master; .nojekyll turns Jekyll processing off entirely.


What's in here

index.html                    shell, hero, the seven tool cards, about, footer
404.html                      error page; pulls its tool list from a shared fragment at load
assets/tokens.css             the palette — every page on the site loads it
partials/
  tool-links.html             the seven tool links, shared with 404.html
  detail/*.html               one per tool — "Use cases" panel, each shipping its own Hide control
  empty.html                  zero-byte fragment; swapping it in collapses a panel
radiology-handbook.html       standalone: radiology IT workflow handbook
hl7-fhir-converter.html       standalone: HL7 v2.5.1 <-> FHIR R4 converter
tools/mirth-transformer.html  standalone: HL7 v2.5.1 -> Mirth transformer builder (a tool card)
tools/theme.js                shared dark/light controller
vendor/htmx.min.js            htmx 2.0.7, vendored
favicon.svg                   boba cup
robots.txt sitemap.xml        /partials/ is disallowed; sitemap covers both repos' pages
.nojekyll                     serve files as-is
CLAUDE.md                     notes for Claude Code

The two reference pages carry no htmx and their own layout CSS, but they share the site palette and theme toggle like everything else. They're in sitemap.xml and linked from the footer's Reference column, but deliberately are not tool cards: the cards drive the filter counts and category tabs, so anything added there has to be categorised and counted.

tools/mirth-transformer.html is the exception that went the other way — it is a card, tagged hl7 interop and counted in the All, HL7 v2.x and Interop tabs. It builds Mirth Connect transformers from HL7 v2.5.1 messages: drag a source field onto a target field and it emits the E4X JavaScript, a pasteable channel <transformer> XML block, and a live preview of the transformed message, with 15 chainable transforms, per-mapping conditions and 33 ready-made recipes. It is also published on its own at HL7-Interface-Javascript-Builder — that copy and this one have to be kept in step by hand, and the standalone copy is deliberately not on this site's palette, since it has no /assets/tokens.css to load.

hl7-fhir-converter.html converts both ways between v2.5.1 messages (ADT, ORM, ORU, SIU, ACK) and FHIR R4 message Bundles, reporting a field-level mapping trace, everything the mapping does not carry over, and a round-trip diff. Its segment field names, code tables and message structures are generated from the same reference data as the HL7 v2.5.1 reference MCP server — change the definitions there and the HL7_SPEC, HL7_TABLES and HL7_STRUCTURES blocks in the converter have to be regenerated to match.

The MCP servers live elsewhere

Two Model Context Protocol servers used to be served from /mcp/ as source downloads, which GitHub Pages could host but never run. They are now standalone repos you can clone, install and deploy:

  • hl7-v2-mcp-server — the HL7 v2.5.1 reference server: segment definitions, message structures, code tables, a parser, and a validator with deep profiles for ORM^O01, ADT^A08, ADT^A31, ADT^A40 and ORU^R01.
  • hl7-v2-fhir-mcp-server — the v2-to-FHIR mapping server: field, datatype and code-value mappings, plus whole-message conversion of ORM, ADT and ORU into a FHIR R4 transaction Bundle.

They are companions, not versions of each other. The reference server answers what does this segment mean; the FHIR server answers what does it become in R4. They default to ports 3000 and 3001, so both can run at once. Both are linked from the footer's Reference column.

Both are work in progress, and the landing page footer groups them under Reference and says so. Keep that label until they are finished.


The landing page is hypermedia

Beyond the theme toggle and a one-line no-js class remover in <head>, the landing page carries no custom JavaScript. Every interaction below is an htmx attribute plus CSS.

The page filters itself

The category tabs don't fetch pre-built per-category fragments. They re-fetch this page and hx-select the subset they want:

<input type="radio" name="view" id="view-hl7"
       hx-get="/" hx-select=".tool-card[data-cat~='hl7']">
<label for="view-hl7">HL7 v2.x <span class="filter-count">4</span></label>

hx-select returns every match, not just the first, so the cards in index.html stay the single source of truth — no card is ever written twice, and there are no category partials to keep in sync. Shared inherited config (hx-target, hx-swap, hx-trigger, hx-indicator, hx-sync) lives once on the enclosing <fieldset>; hx-sync="this:replace" cancels an in-flight request when you click another tab.

The tabs are real radio inputs, so :checked drives the active styling in pure CSS and the group stays keyboard-navigable. The live "N shown" readout is a CSS counter over .tool-card — which is why it sits after the grid in the markup, since a counter only sees elements preceding it in document order.

Panels open and close over the wire

Each card's Use cases button hx-gets its fragment; the fragment ships its own Hide details control, which hx-gets the zero-byte partials/empty.html back into the same target. Both directions are plain hypermedia exchanges. :has(.tool-detail > *) hides the trigger once a panel is in, and .htmx-request drives the spinner. This keeps working on cards htmx swapped in via a filter, since htmx processes swapped content.

What it deliberately doesn't do

The tool cards stay in index.html rather than being fragment-loaded. Crawlers and no-JS visitors get the complete tool list, descriptions, and links in the initial response; htmx only ever adds content — the detail panels, and nothing else. Nothing crawlable is deferred.

hx-boost is scoped to the two brand links — the only same-origin, same-shell navigations on the page. It is not global: the tool links go to the other repo's apps, which ship their own <head>, styles, and scripts, and boosting those would swap bodies across documents that don't share a shell.

hx-push-url is not used on the filters. GitHub Pages can't serve a filtered state on refresh, so a pushed URL would 404 or lie about what the page shows.

/partials/ is disallowed in robots.txt so fragments aren't indexed as thin standalone pages.


Theme

Dark is brown sugar boba, light is milk tea. Accents come off a boba shop's flavour wall:

Token Dark Light
--bg #1e1815 #f3e7d6 steeped pearl / milk tea
--surface #161110 #fdf8f0 dark cup / milk foam
--accent #c9a0ea #67399c taro
--accent2 #f2a0bd #a83464 strawberry milk
--green #a9c96a #4d6b1c matcha
--yellow #f0cf8a #7a5a0e brown sugar
--red #f0736f #b03530 lychee
--orange #eb8a3c #9c4d13 thai tea
--text #f7efe4 #2b211a milk foam

Taro leads for a reason. The seven tool cards each set --tint to one of these, and a milk-tea tan accent landed within a few degrees of hue of the thai-tea orange — two cards would have looked identical. Taro sits ~200° away. If you retheme, keep the tint hues separated by at least ~12°.

Both token blocks live in assets/tokens.css, and every page on the site loads it — landing, error, tool and reference alike. That file also carries the shared chrome: the focus ring, ::selection, the .site-bar breadcrumb, the .btn-theme toggle and the .site-foot strip. A page's own <style> block is layout only.

Pages speak one vocabulary — --bg --surface --surface2 --sink for surfaces, --border --border-soft for rules, --text --body --muted for type, the accents above, --*-bg and --*-line for derived washes, and --on-accent for text on a solid fill. The reference pages hold a semantic convention on top of that: HL7 v2 is --yellow, DICOM is --cyan, FHIR is --green.

Two rules hold the palette together:

  • No page declares its own colours. No hex, rgb() or rgba() in a page stylesheet, with three deliberate exceptions: black shadow and scrim alphas, the handbook's greyscale densitometry wedge, and the handbook's @media print block, since print is always ink on white. Everything else resolves through a token, so changing assets/tokens.css moves the whole site at once.
  • Contrast is checked, not eyeballed. Every foreground clears WCAG AA against --bg, --surface, and both glass fills (--glass, --glass-strong) in both modes. Worst pair is currently 4.94:1. The glass fills matter: a colour can pass on the page background and still fail on a frosted card.

Motifs are CSS masks so they inherit currentColor and work in both modes without a second asset: --pearl-glyph (three tapioca pearls, the section-kicker bullet) and --pearl-band (pearls settling along the footer edge). The brand mark and favicon are a boba cup.

tools/theme.js sets data-theme on <html> before first paint, defaults to dark, exposes window.toggleTheme(), and persists to the localStorage key hl7-tools-theme. That key is shared with the tools repo, so a visitor's choice follows them between the landing page and the tools. Every page carries a #btn-theme button and the controller wires it automatically; storage access is guarded, so a browser with site data blocked simply stays on dark.

Note: the tool pages in hl7-dicom-tools carry their own copy of the theme and are not yet on this palette, so they won't match the landing page until they're updated there.


Editing

Changing a tool's copy? Edit the card in index.html and its partials/detail/*.html fragment. Descriptions are deliberately not duplicated in this README — the cards and fragments are what the site actually serves.

Adding a tool? Four places, or the page will lie about itself:

  1. a .tool-card in index.html, with data-cat and a --tint
  2. the filter-count numbers on the affected category tabs, and the "N tools" heading
  3. partials/tool-links.html (feeds 404.html) and the footer's Tools column in index.html
  4. sitemap.xml

A new page starts by linking /assets/tokens.css and /tools/theme.js, adding a #btn-theme button and a .site-bar breadcrumb, then writing layout CSS that only ever reads the shared tokens.

Retheming? Edit assets/tokens.css — that is the only place colours are defined — and re-check contrast against all four surfaces in both modes.


Privacy

Client-side only — no data leaves your browser. All parsing, rendering, and modification happen locally in the browser. No files, HL7 messages, or DICOM images are uploaded anywhere. That makes these tools safe for inspecting data that may contain PHI. The site sets no cookies and runs no analytics.


Running locally

Serve the repository root over HTTP:

python3 -m http.server 8000

It must be HTTP, not file:// — htmx fetches the fragments in partials/ with XHR, and the page references /vendor/, /tools/, and /partials/ by root-absolute path. Both break on a file:// origin.

One gotcha when editing fragments: python3 -m http.server sends no cache headers, so a browser will happily serve you a stale partials/*.html while the page itself reloads fresh. If a fragment edit doesn't show up, hard-reload or restart on a different port.

The tools themselves run from their own repository — see hl7-dicom-tools.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages