Developer guide
How to continue building simple-edep — with or without an AI coding tool. The second half of this guide focuses on working with Claude Code specifically, with concrete examples drawn from this project's own development history.
Source: pleuston/simple-edep (app) · pleuston/simple-edep-edh (data)
1 · Architecture
simple-edep is a no-build-step static site. There is no bundler, no transpiler, no framework — plain HTML, CSS, and ES5 JavaScript served directly by GitHub Pages. The only exception is the jinn-codemirror web component, which is loaded as a native ES module from a vendored bundle.
Two-repo split
The corpus data (82 k EpiDoc records, index, people, photos, bibliography, geodata) lives
in a separate data repo
(simple-edep-edh)
and is served by jsDelivr, a free CDN (Content Delivery
Network — a global network of servers that cache and distribute files faster than a
single origin server). jsDelivr mirrors public GitHub repos automatically; the app fetches
data from cdn.jsdelivr.net/gh/pleuston/simple-edep-edh@main/… rather than
from GitHub directly. This keeps the app repo lean (no large binary blobs, fast Pages
builds) while jsDelivr provides CORS headers, gzip compression, and global caching.
| Repo | What it contains | How it is served |
|---|---|---|
simple-edep | HTML, CSS, JS, vendor libraries, build scripts | GitHub Pages (direct) |
simple-edep-edh | EpiDoc XML records, JSON indexes, people, photos, geo | jsDelivr CDN (CORS, gzip) |
jsDelivr cache TTL: the @main tag has a
roughly 12-hour CDN cache. After pushing to the data repo, changes may not be visible on
the live site for up to 12 hours. To bypass the cache during development, use a commit SHA
in the URL, e.g. @a1b2c3d. During local development, point
collections.js directly at the data repo's local path instead of jsDelivr.
2 · Key files
| File | Role |
|---|---|
styles.css | All styles. Single flat file, no preprocessor. EDEP typographic theme (two serif variables + accent red). |
auth.js | GitHub session. Checks localStorage for a stored PAT and username. Loaded first on every page via <script src="auth.js"> (synchronous, before DOMContentLoaded). Exposes EpiAuth.isSignedIn(), EpiAuth.getUser(), EpiAuth.signOut(). |
collections.js | Collection registry. Defines EDH and I.Sicily base URLs, URL resolvers (EpiCollections.recordUrl()), the merged catalog loader (EpiCollections.loadCatalog()), and EpiCollections.getJSON() for cached fetches. |
catalog.js | Inscriptions register: load index, filter/sort/paginate, row HTML, open-in-editor logic, XML→form parser (parseRecord()). |
people.js | Persons register: load people.json, filter by name components (praenomen, nomen, cognomen…), sex, social state, occupation. |
map.js | Geodata: 3-column layout (filter sidebar, entry list, Leaflet map). Filters sync between list and map markers. |
app.js | Editor form. A SECTIONS array drives the entire metadata form via render() / fieldControl(). To add a metadata field, add an entry to the array. |
generator.js | EpiDoc/TEI serializer. EpiDocGen.buildEpiDoc(state) turns the form state object into well-formed XML. |
reading.js | EpiDoc→HTML renderer. Converts the EpiDoc XML to a reading view with critical apparatus, translation, find-spot, and bibliography. |
panel.js / recordview.js | Right-side reading panel used by the Inscriptions and People registers. RecordPanel.open(id, col) fetches and displays a record without leaving the page. |
favorites.js | localStorage-backed starred inscriptions and named collections. EpiFav.button() renders the ★ button inline in register rows. |
scripts/import-edh.js | Node build script. Reads EDH CSV/JSON exports from ~/Downloads/edh/ and writes slim JSON to the data repo. Run once when source data changes. |
scripts/build-index.js | Node build script. Scans records/*.xml and writes collections/edh/index-*.json. Run after adding or editing EpiDoc records. |
3 · Running locally
Because the site uses fetch() calls that require an HTTP context (not
file://), run a local server from the app repo root:
cd ~/repos/simple-edep python3 -m http.server 8765 # open http://localhost:8765
By default, collections.js reads the data from jsDelivr. To test data
changes before pushing, temporarily swap the base URL:
// in collections.js, change: base: "https://github.com/pleuston/simple-edep-edh" // to: base: "http://localhost:8766" // second server on the data repo
cd ~/repos/simple-edep-edh python3 -m http.server 8766
Build scripts
# regenerate all JSON registers from the EDH CSVs in ~/Downloads/edh/ node scripts/import-edh.js # regenerate the inscription index from records/*.xml node scripts/build-index.js
Both scripts write into ../simple-edep-edh/ (the sibling data repo
directory). Commit and push the data repo after running them.
4 · Working with Claude Code
Claude Code is Anthropic's agentic coding tool. It can read files, run shell commands, edit code, and commit to git — all from a conversation. You describe what you want; it investigates, proposes, and implements.
Setup
- Install the Claude desktop app or the CLI:
npm i -g @anthropic/claude-code - Open a terminal in the app repo root and run
claude. - Claude reads the directory structure and git history automatically. No project
configuration file is required, though a
CLAUDE.mdat the repo root can pre-load important constraints (like "no bundler, no build step").
Memory
Claude Code maintains a persistent memory in
~/.claude/projects/<repo-path>/memory/. Across sessions it remembers
project facts, constraints, and your preferences. You can ask it to "remember" anything
important, or it records things automatically when you correct its behaviour.
What it can do in this project
- Read any file, run
grep, check git log. - Edit HTML, CSS, JS — understanding how the pieces connect.
- Run the build scripts (
node scripts/import-edh.js) and commit results. - Push to GitHub when you say "push".
- Take and read browser screenshots (via the Preview panel in the desktop app) to verify visual changes.
5 · Prompting effectively
Describe what you see, not what to code
You do not need to know the implementation. Describe the problem or the desired outcome in plain language and let Claude read the code to figure out how to achieve it.
| Less effective | More effective |
|---|---|
| "add filter dropdowns to people.html and update people.js to filter by nomen and cognomen" | "the people section is still very hard to navigate and understand. allow to filter and search like on the EDEP site form (screenshot attached) and make it much more compact" |
| "change the CSS grid to three columns" | "geodata also needs a dedicated search and filter form left to the map, then entries list, then map" |
| "gate the edit button on EpiAuth.isSignedIn()" | "each entry and person needs to be editable after login" |
Attach screenshots
Claude can read images. A screenshot of another site you want to emulate (like the original EDEP person form) is worth a paragraph of prose. Drop it into the conversation alongside your message. Claude will inspect the UI, infer the fields and layout, and implement a matching version.
One change at a time
Multi-part requests ("redesign the layout AND add filters AND change the nav AND push") work, but each part is done sequentially. For complex tasks, it is easier to verify each step before moving on. In practice, short telegraphic follow-ups work well:
- "looks good, now push"
- "also gate the edit button on login"
- "add tribus as a dropdown too"
Let it read before it acts
Claude reads the relevant files first before writing anything. If you interrupt a reading step, it may propose a change without the full context. Let it finish the investigation before saying "go ahead".
6 · Worked examples from this project
Example A — People register: name-component filters
Prompt: "for the persons also filter for the values of Deity, Praenomen, Nomen Gentile, Cognomen, Supernomen, Tribus, Origo, Sex — like in P000001 some are empty" (with an EDEP screenshot attached)
What Claude did:
- Read
people.jsand found the current data only stored the combinednamestring — no individual components. - Read
scripts/import-edh.jsto see how the CSV is parsed. - Checked the CSV headers (
praenomen,nomen,cognomen,supernomen,tribus,origocolumns exist). - Updated the import script to write those six fields to each JSON record.
- Re-ran
node scripts/import-edh.js— regenerated 93,643 records (18 MB). - Committed and pushed the data repo.
- Updated
people.htmlto add nine filter inputs/selects. - Rewrote
people.jsto wire each field, filter, and display the structured name components in rows. - Committed and pushed the app repo.
Total clock time: a few minutes. Without an AI tool, steps 1–9 would each require opening files, understanding the data model, writing code, and testing — easily an hour or more.
Example B — Geodata: 3-column layout
Prompt: "geodata also needs a dedicated search and filter form left to the map then entries list then map"
What Claude did:
- Read
map.html(single<div id="map">, no filters) andmap.js(markers loaded once, no interaction with a list). - Checked the catalog and people pages for the existing filter sidebar CSS pattern
(
.with-facets,.facet-side). - Rewrote
map.html: three-column grid — filter sidebar, scrollable list, map panel. - Rewrote
map.js: filter state, filter predicate, paginated list render, cluster marker sync (filtering hides markers for non-matching results), bidirectional link (clicking a list row pans the map; clicking a marker scrolls the list). - Added CSS for the
.map-layoutgrid and responsive collapse. - Took a browser screenshot to verify the layout before committing.
Example C — Secondary nav restructure
Prompt: "Favorites / Log / About / Instructions [shall go to the right]. favorites only an icon. instructions shall become Guide"
What Claude did:
- Listed all HTML pages (12 files).
- Used a single
perlone-liner to remove the four links from every primary nav and inject the new secondary<nav class="sitenav-right">block into every.topbar-right— one command, all pages. - Added CSS for
.sitenav-rightand the icon-only Favorites button. - Delegated active-state marking to
auth.js, which already had thepagevariable derived frompathname.split("/").pop().
Lesson: when the same change needs to be applied to many files, Claude will reach for sed/perl/awk rather than editing each file manually. Tell it the number of pages if it is not obvious from a directory listing.
7 · Common development recipes
Add a filter to a register page (People / Inscriptions / Geodata)
- Check whether the data field already exists in the JSON (read a sample record
in the browser console:
EpiCollections.getJSON(…).then(d => console.log(d[0]))). - If not: add the field to
scripts/import-edh.js, runnode scripts/import-edh.js, push the data repo. - Add an
<input>or<select>to the facet sidebar in the HTML. - In the JS: grab the element, add an event listener that calls
render(), extend thematches()function. - For a select: populate it after data loads by collecting unique values with
forEachand appending<option>elements.
Add a metadata field to the editor
- Add an entry to the
SECTIONSarray inapp.js. Each entry has akey(state property name), alabel, and atype(text,select,textarea,datalist,row, etc.). - Add the corresponding serialization in
generator.js— find where the parent element is built and add the new child element. - Add the corresponding deserialization in
catalog.js'sparseRecord()function, so records round-trip correctly when opened for editing.
Update the corpus data
# 1. Place updated CSVs / zips in ~/Downloads/edh/ # 2. Regenerate JSON registers node scripts/import-edh.js # 3. Regenerate inscription index (if EpiDoc XML changed) node scripts/build-index.js # 4. Commit and push data repo cd ../simple-edep-edh git add collections/ git commit -m "Refresh EDH data" git push
Test a data change without waiting for jsDelivr
# Terminal A — data repo cd ~/repos/simple-edep-edh python3 -m http.server 8766 # Terminal B — app repo cd ~/repos/simple-edep # Temporarily edit collections.js: change the base URL to http://localhost:8766 python3 -m http.server 8765 # Open http://localhost:8765 in the browser # Remember to revert collections.js before pushing!
8 · Constraints to keep in mind
- No bundler, no transpiler. All JS must be valid ES5 (or plain
ES2015 at most — but avoid
import/exportoutside the onetype=modulescript tag for jinn-codemirror). Arrow functions, template literals,const/let, and destructuring are fine in modern browsers;import()dynamic imports are not used. - No external runtime dependencies. Leaflet, OpenSeadragon, and
jinn-codemirror are vendored under
vendor/. Adding a new library means copying it there, not adding apackage.jsondependency. - CORS. Every
fetch()must target a CORS-enabled endpoint. jsDelivr, the GitHub API (api.github.com), and heidICON IIIF all sendAccess-Control-Allow-Origin: *. Rawraw.githubusercontent.comURLs do not — use jsDelivr instead. - Large JSON files.
people.json(18 MB) and the split index files are large. Always paginate; never load all 93 k people into the DOM at once. jsDelivr gzip brings transfer sizes down to a few MB, but parsing still takes a noticeable moment. - heidICON images are not re-hosted. Photographs from the Epigraphische Fotothek Heidelberg are hotlinked via the heidICON IIIF server and attributed on every display. Do not copy them into either repository.
- I.Sicily IIIF images are not embeddable. I.Sicily records are displayed without images; only EDH records have IIIF photo access via heidICON.
9 · Tips from experience
Give Claude the "why", not just the "what"
This codebase's constraints (no bundler, two-repo split, jsDelivr cache) are not obvious from the code alone. State them explicitly in your first message of a session, or keep them in the project memory. Claude will otherwise sometimes propose solutions that introduce a build step or rely on Node at runtime.
Use browser screenshots to verify UI changes
The Claude desktop app has a preview panel that can take screenshots of a running local server. After a UI change, ask Claude to "take a screenshot and check it" before committing. This catches layout bugs (collapsed maps, hidden elements, wrong column widths) without leaving the conversation.
Ask for a plan before complex changes
For multi-file changes — like adding a new register page or restructuring the editor form — ask Claude to "outline what files you'll change and why" before it starts editing. This surfaces misunderstandings early and lets you redirect without undoing committed work.
Commit frequently, push when stable
Claude commits after each logical unit of work. You can push manually ("push when ready") or say "push" at the end of a session. The git log becomes a readable history of each feature step.
The memory system is your handoff document
Claude stores project facts, your preferences, and non-obvious constraints in
~/.claude/projects/…/memory/. Before ending a session, ask it to "save
anything new you learned about the project." The next session will start with that
context already loaded.
Check jsDelivr cache after data pushes
After pushing to the data repo, the live site may show stale data for up to 12 hours.
To force a refresh during development, append ?v=1 to fetch calls (jsDelivr
ignores it but the browser treats it as a new URL). For a permanent fix, use a specific
commit SHA in the CDN URL instead of @main.
This guide was written with Claude Code and reflects the actual development history of this site. See the commit log for a step-by-step record.