2026-10-25 22:10
Two Locks and a Hidden Door
A quiet Sunday of locksmithing. Nothing a reader will notice, which is the nature of the work.
It started with a dream. I woke up convinced I had built an admin page listing registered users, and went looking for it. The file was there β users.php, in the admin directory, zero bytes. I had created it and never written a line. That is a strange feeling: finding the shape of an intention with nothing inside it.
So it got written. Every account with its verification state, status, failed login count, last login, registration date, newest first. Read-only on purpose. Nothing on that page changes anything, because account repairs should stay deliberate and manual, done by someone who meant to do them.
The first version was a table. Eight columns, which looked fine on a monitor and turned into one letter per line on a phone β every cell a vertical column of characters, every date stacked like a tower. A table forces its columns to share whatever width is in front of them. A block just gets taller. So each account became a card instead, labels beside values, and the same page now reads the same on either screen.
WHO ARE YOU
The users table gained a role column. Everyone is 'user' unless told otherwise; one account is 'admin'. SessionManager learned to answer two questions: what role is this person, and refuse them if it is not admin. The role is looked up fresh on each page rather than stored in the session, so changing someone's role takes effect on their next page view instead of their next login.
Then the admin directory got a guard. One small file, _guard.php, holding three lines, and one line at the top of every page in that directory pointing at it. Fourteen files. The alternative was pasting the same three lines fourteen times and remembering to paste them again in every page I write from now on, which is how a rule becomes a thing you used to follow.
The menu item for Admin is now hidden from anyone who is not an administrator. Worth being clear with myself about what that accomplishes: nothing, defensively. Hiding a link does not protect the page behind it; anyone can type a URL. The guard protects the page. Hiding the link is courtesy β it stops offering a door that will not open.
I logged in as my wife to check, and was turned away from the admin pages exactly as a stranger would be. That is the only way to know a lock works: try it from the outside.
DID YOU MEAN TO DO THAT
The second half of the day was CSRF, which was new to me and turns out to rest on an old idea I had been getting wrong for years.
A link is supposed to be safe. Fetching a URL should tell you something, never change something. That is what GET means. POST is for changing. I had always treated the choice as cosmetic β POST to keep things out of the address bar, GET when I wanted a link I could bookmark. But links get followed by things that are not people: browser prefetchers, virus scanners, mail providers checking for malware. In 2005 a Google tool quietly emptied admin panels all over the web by prefetching every "delete" link it found. Nobody had done anything wrong by their own lights.
The forgery problem is the other half. Another site can make your browser issue a GET just by embedding an image tag pointing at your URL. It cannot make your browser submit a form carrying a secret it is not allowed to read. That secret is the CSRF token.
So the guard now refuses any POST into the admin directory that does not carry a valid token, and each form carries one. Again: the check lives in one file, not fourteen.
While putting it in I found that CSRF was already implemented twice on this site β once properly in TokenService, and once privately inside verse_edit.php β and that the two store their token under the same name while disagreeing about its format. One writes a timestamp beside the token; the other does not. Whichever page you visit first decides whether the next one works. That is almost certainly behind a "Security token invalid" message I hit last night and blamed on something else.
WHAT ELSE TURNED UP
Walking the directory produced the usual sediment. A scratch SQL query from January that generated one of the bridge pages by hand β the ancestor of the fused-word problem I fixed on Friday. A four-line file whose only purpose was to answer "does PHP run in this folder." Two changeblog exports.
And a copy of the commentary editor sitting in the public root, from January 8, left behind when the tool moved into the admin directory. It does not work β its include paths still assume its old home, so it fatals on load. But it fatals loudly, printing the full server path and PHP include path to anyone who asks for it. A broken tool that still leaks.
LESSONS
A hidden link is not a lock. It is a closed curtain.
Put shared behavior where every file already looks. The pages in that directory all load one file; that file is the natural place for anything they all need.
Choose GET or POST by asking whether the request changes something, and the rest sorts itself out.
And the thing that keeps proving true: the directory is the unit. Going through every file in one folder, one at a time, found three stray copies, four empty files, a leaking error page, and a duplicated security implementation. None of those would have surfaced by fixing only what was broken.
2026-10-25 01:52
Every entry before this one was wrapped by hand at seventy-eight characters, because at some point lines ran past the right margin and hand-wrapping made them stop. That fix worked, which is exactly what makes it hard to question. A workaround that succeeds stops looking like a workaround and starts looking like the way things are done.
The stylesheet says the text should have wrapped on its own all along. The body of a post is set to preserve whitespace and wrap, and it sits inside a container with a maximum width, which is the ordinary arrangement for exactly this. So either the stylesheet changed after I started wrapping by hand and nobody went back to check, or something else was pushing the column wider than it looked.
This entry is the test. Nothing in it has been wrapped. Every paragraph is a single long line, and the column is being left to decide where the breaks go. If it reads normally, then hand-wrapping has been unnecessary for some time, and there is a real gain in dropping it: text that wraps on its own reflows to fit a phone, while text wrapped at seventy-eight characters stays seventy-eight characters wide no matter how narrow the screen gets, and runs off the edge of it.
There is one case the stylesheet genuinely cannot handle, and it is worth including here deliberately rather than discovering it later in a post that matters: a single unbroken token longer than the column, such as /home/u150893401/domains/rghollenbeck.com/private/asv_users/db_config.php, which has no spaces in it and therefore nowhere to break. If that path is hanging out past the right edge while this sentence behaves itself, the diagnosis is narrow and the fix is one declaration.
I am recording this one even though it is small, because the useful part is not the line breaks. It is that I had an explanation I believed, built a habit on top of it, and never went back to see whether the explanation was still true.
2026-10-25 00:35
2026-10-24 17:25
Two Translations, and a Door That Was Never Locked
Three weeks since the last entry, and the work divides cleanly in two. Most of
it went to putting a second translation on the site. The last few hours went to
closing a security hole that had been standing open since October 4 β the very
day I wrote the entry below congratulating myself on production-grade
authentication.
A SECOND TRANSLATION
The site now carries the World English Bible alongside the American Standard
Version, and a reader can move between them from any page.
The WEB arrives from eBible.org as USFX, which is XML, so it needed a
converter rather than a parser. The first version of that converter quietly ate
text. It walked the document with a flat iterator and collected each element's
tail β the text that follows a tag β which works until text lives inside a tag
rather than after it. Poetry is full of such tags. Psalm 3:1 came through as
"are they that rise up against me" and I only noticed because the line didn't
scan. A recursive walker fixed it, and the fix is the kind that either works
everywhere or fails loudly; there is no quiet middle.
The second problem was Psalm 119. The converter stores a psalm's
superscription as verse 0, matching the convention already on the site, and
USFX marks superscriptions with a descriptive-title tag. Psalm 119 has no
superscription. What it has is twenty-two acrostic section headings β ALEPH,
BETH, GIMEL β marked with the same tag. All twenty-two tried to become verse 0
of the same chapter, and twenty-one of them would have silently overwritten the
one before.
They would have. The import failed instead, because the converter now refuses
to emit two rows with the same book, chapter, and verse. I had verified the
first import by counting verses per book and comparing against a reference
table, and every book matched. It matched because the count excludes verse 0 β
that is what makes superscriptions invisible to the arithmetic in the first
place. A check that excludes a thing cannot detect a collision in the thing it
excludes. The uniqueness assertion now sits in the converter permanently, and
it costs nothing.
A third defect never happened, which is the point of mentioning it. Two
flush tags with no whitespace between them will fuse the last word of one line
onto the first word of the next. The converter does not trust the source's
whitespace at all: every tag that isn't on a short whitelist of word-internal
tags forces a space in front of itself, and the whitespace collapses afterward.
THE ASV, SOURCED AGAIN
The reason that defect was on my mind is that the ASV already had it. The table
contained madethe, isa, Butthe, givetheethe β four known cases, and no way to
know how many unknown ones.
The cause turned out to be meaningful rather than random. The eBible plain-text
ASV wraps translator-supplied words in square brackets: the words the 1901
translators set in italics because they are not in the Hebrew or Greek but are
needed for English. Whoever prepared the file I was using stripped the brackets
without preserving the space that had been adjacent to them. The corruption was
not noise. It was information being destroyed.
So the ASV was re-sourced from eBible's signed release, converted properly,
verified, and swapped in as a single transaction: 31,218 verses. The brackets
survived the trip this time, and verseText() renders what is inside them in
italics β which restores, on the screen, exactly what the 1901 printers put on
the page. A ninety-year-old typographic convention, lost in a text file,
recovered.
The same function sets Selah in italics, but only in Psalms and in Habakkuk. In
Isaiah 16:1 "Selah" is not a musical notation at all; it is the Edomite city,
the one later known as Petra. One verse in the whole Bible, and it would have
been styled wrong forever.
SWITCHING
The picker is a select element in the page header and in the reading plan. It
submits itself with JavaScript, and falls back to a button when JavaScript is
off. The choice rides in the query string, gets stored in a cookie, and the URL
is cleaned up by a redirect so nobody bookmarks a page with a translation
pinned to it by accident.
The part worth recording is a constant that is deliberately not a variable. The
reading plan's cursor is a verse offset counted from the start of a stream's
book list β not a book-and-chapter pair. Two translations that disagree about
whether a verse exists will disagree about what offset 4,118 means. So the plan
reads from one translation, named in its own constant, no matter which
translation the reader is looking at. A reader can switch versions all day and
the cursor cannot move. Display and position are separate concerns, and the
place where they would have been confused is the place where the damage would
have been invisible and permanent.
HOUSEKEEPING THAT PAID
Three things that felt like chores and were not.
The page-level JavaScript scattered across the site moved into one app.js. In
the course of that I discovered the reading plan was never loading app.js at
all, which is why its translation picker did nothing for an afternoon.
The canon β the document that tells any future collaborator, human or
otherwise, how this site is built β was rewritten to describe what the code
actually does rather than what it did in week one. Linking rules, a new rule
about translations, a dated amendment log. Then a report script that greps for
violations: nine checks, changes nothing, writes a list. The first run found
3,447 problems. After cleanup it finds 121.
One of those 3,447 was oil.php, a topical study page carrying 198 lines of
Scripture pasted in by hand β including treadtheirwinepresses and Agoodname.
The corruption had been propagating out of the database into handwritten pages,
where no database fix would ever reach it. All 198 lines are now one function
call that queries the current translation. Fix the data, fix the page.
THE HOLE
Late in the day I noticed a small round artifact at the bottom of the
registration form β a broken image where a profile picture would go. Harmless
clutter. I went looking for it anyway, and to see what the page did I walked
through a real registration.
The account was created. The verification email was sent, correctly, and
arrived. And then I logged in.
The authentication routine checked for locked accounts and for suspended
accounts. It checked the password properly, with Argon2id, exactly as described
in the October 4 entry below. It never once looked at email_verified. The
column was being written faithfully by the registration process and read by
nothing. Every piece of the verification system worked except the one place it
was supposed to matter.
Anyone could have registered with an address belonging to someone else and used
the account immediately.
The fix is nine lines and one word. The column is now selected, and the check
sits after the password is verified rather than before β so a wrong password
and an unverified account give the same answer, and the login form cannot be
used to find out which addresses have registered here.
LESSONS
A feature can be complete, tested, documented, and never called. Email
verification was built on October 4, worked perfectly for twenty days, sent
real mail to real inboxes, and protected nothing.
A verification that excludes a category cannot find errors in that category. My
verse counts were correct and proved nothing about verse 0.
Corruption spreads into handwork. Four fused words in a database became
hundreds of fused words in pages that were hand-assembled from it, and those
copies outlive the fix.
The bug is rarely where the symptom is. A broken image at the bottom of a form
led to an open front door. It is worth chasing small ugly things.
And one about process: when I take in five instructions at once I perform four
of them. Not because any single step is hard, but because the step I skip is
the one I never consciously decided to skip. Work that arrives one task at a
time gets finished. Work that arrives in a list gets mostly finished, which is
worse, because mostly-finished looks done.
WHAT'S NEXT
Registration still bounces a new user to the login page after a two-second
refresh instead of telling them to check their email β which was confusing
before the gate existed and is wrong now that it does.
The reading plan service still names the ASV table directly in five queries and
needs to go through the same function as everything else.
The search page has a typo that makes page two of any result set fatal.
And the footnote admin page promised on October 10 is still promised.
2026-10-10 00:11
The daily reading page now carries clickable footnotes, using the same mechanism running on the chapter pages. When a verse in today's portion has commentary
attached to it, a small superscript number appears at the head of the verse, and the
note itself waits at the bottom of the page.
The first one to go live was Job 1:6, where a note on "Satan" explains that
*ha-satan* in the Hebrew Scriptures is a title meaning "the accuser," while the New
Testament uses Satan as a proper name for a personal adversary. The note links out to
the Invisible Creatures bridge page for anyone who wants the longer treatment.
Numbering runs continuously down the whole page rather than restarting in each
reading. A morning with four portions β an Old Testament book, a Gospel, a few verses
of Proverbs, and a psalm β produces one sequence and one list. If Job supplies three
notes and the psalm supplies a fourth, that fourth note is number four. The
alternative, restarting the count in every section, would have put three different
notes labeled "1" on a single screen.
The granularity is deliberate. A footnote attaches to a verse, not to a word. There
is no character offset and no anchor phrase to match, so nothing goes wrong when a
word appears twice in a verse or not at all. The attachment is three integers: book,
chapter, verse. A verse may carry more than one note, in which case two markers
appear side by side and each resolves to its own entry below. That is enough for the
kind of note actually being written here, which tends to be a short essay about how a
term functions across the canon rather than a gloss on a single word. Word-level
anchoring earns its complexity for textual variants β *some manuscripts read* β and
that is not the work in front of us. The column can be added later if it ever is, and
the existing verse-level rows would keep working underneath it.
What makes this worth the trouble is that a note is written once and can hang on as
many verses as it belongs to. The Satan note could attach to Job 1:6, Job 2:1,
Zechariah 3:1, and Matthew 4:10 at the same time, and correcting a sentence in it
would correct all four places at once, in the daily readings and the chapter view
alike. The page is a template; the note is data. Nothing about the commentary lives
in the PHP.
A second change went in the same evening, smaller but easy to get wrong later. The
reading plan divides the Old Testament into five categories and rotates through them,
and the Wisdom category had been defined as Job through Song of Solomon β which
includes Psalms and Proverbs. Since those two books now have dedicated daily tracks
of their own, the rotation would eventually have walked through them a second time,
from a different position, on the same morning. Wisdom is now Job, Ecclesiastes, and
Song of Solomon.
The timing mattered more than the change itself. Each stream's cursor is a verse
offset counted from the beginning of that stream's book list, not a book-and-chapter
pair. Job sits first in the list either way, so while the cursor is still somewhere
inside Job's forty-two chapters, the same stored number means the same verse before
and after the edit. Make that change after the cursor had crossed into Psalms and the
number would have silently pointed somewhere else entirely.
Still ahead is a small admin page for the footnotes themselves, so that writing
commentary stops meaning editing a database row by hand.
2026-10-09 01:19
A Reading Plan That Paces Itself
OVERVIEW
Two kinds of work today. The morning went to bugs that had the Scripture reader
partly broken. The afternoon went to something better: rethinking how the daily
Psalms portion is calculated, so that the plan adapts to the reader rather than the
other way around.
WHAT WAS FIXED
The chapter navigation arrows had been throwing a database error on every page.
It was caused by a variable name. The menu partial builds its own connection to
the users database and calls it $pdo, which silently overwrote the Scripture
connection in every page that includes the menu. The chapter text rendered correctly
because those queries run before the menu loads; only the navigation, computed
afterward, reached for the wrong database. Renaming the menu's connection to
$usersPdo defuses the same trap on every page that includes it, not just this one.
Login was broken by a simpler inconsistency: the users database was named one way in
secrets.php and another in the config that loads it. That is corrected, and the old
mysqli connection file now routes through the same db.php as everything else, so
there is one door into the databases instead of three.
The two-year plan was reporting that it ended today. Marking a day complete
recalculated the end date from the current day number rather than from the plan's
duration, which meant the end date advanced to today's date every single morning. I
removed the recalculation rather than correcting it. The end date changes only when
the plan changes, and a value that never moves should not be rewritten daily.
Finally, any reading that crossed from one chapter into the next returned no verses
at all. The query had been ranging chapter and verse independently, which produces
conditions like "verse at least 5 and at most 2" β impossible, and silently empty.
Chapter and verse are one position, not two coordinates, and the query now treats
them that way.
WHAT WAS BUILT
The Psalms calculation now works from a stored cursor instead of a fixed slice of the
day number. Each morning it asks two questions: how many verses remain, and how many
days remain. Today's portion is the first divided by the second, rounded up. That is
the whole of it.
It also refuses to span more than one psalm. If the rest of the current psalm is
within reach it finishes it; otherwise it stops short and resumes there tomorrow. A
psalm is a complete thought, and ending one reading partway through while the next
day opens somewhere else serves nobody.
WHY IT WORKS THIS WAY
Rigid plans do fail on arithmetic, but that is not what makes people abandon them.
They quit because falling behind becomes a visible backlog, and the backlog becomes
guilt, and a page announcing that you owe four days of reading is easier to close
than to face.
This plan cannot produce that state. The shortfall is absorbed into tomorrow's
portion before the reader ever sees it. Miss a week in February and it hands you
slightly more each day for a while, and still finishes on October 5, 2028.
Today's portion is two verses, the closing verdict of Psalm 1. Short enough to carry
around all day.
LESSONS LEARNED
A variable name is an interface. $pdo meant Scripture in one file and users in
another, and nothing in PHP was ever going to warn me. That cost two hours.
"It must be cached" is what you say when you do not yet have a mechanism. Running the
database file from the command line, in isolation, found the real cause in about
thirty seconds. That should have been the second thing I tried rather than the
twentieth.
A value computed in two places will eventually disagree with itself. Three of today's
four bugs were exactly that.
WHAT'S NEXT
Superscriptions need to appear in the daily reading view. They are excluded from the
verse count, correctly, but should still be shown when a reading opens a psalm.
The Old and New Testament streams still use the old fixed-slice logic, which advances
position more slowly than it consumes chapters and therefore repeats readings. The
Psalms pattern transfers directly once I get to it.
Proverbs will probably keep its current behavior. Chapters 10 through 29 are
collections of independent couplets, so splitting one mid-chapter costs the reader
nothing.
2026-10-05 04:12
2026-10-04
Production-Grade User Authentication System β October 4, 2026
OVERVIEW
Today I completed a comprehensive overhaul of the user authentication
infrastructure for rghollenbeck.com. This was not a semester project, but
a serious attempt at a real-world, production-grade solutionβthe kind of
security you'd expect from an ecommerce platform.
WHAT WAS BUILT
1. Secure Password Management
- Argon2id hashing with aggressive parameters: 65,536 MB memory, 4
iterations, 2 parallel threads
- One-way irreversible password storage
- Password strength validation (minimum 12 characters, mixed case,
numbers, symbols)
- Password reset tokens with 1-hour expiry and one-time use only
2. Account Protection
- Rate limiting: 5 failed login attempts triggers a 15-minute account
lockout
- Automatic tracking of failed login attempts per user
- Account lockout status with manual reset capability
- Audit logging of all authentication events (login success/failure,
password reset, email verification, account lock)
3. Session Security
- Secure PHP sessions with HttpOnly and Secure cookie flags
- 2-hour session timeout for auto-logout after inactivity
- Session fixation protection via regeneration on login
- CSRF token protection on all forms using cryptographically random
tokens with configurable salt
4. Email Verification
- Email verification tokens with 24-hour expiry
- One-time use tokens that prevent reuse
- Automatic token cleanup of expired records
- HTML and plain-text email templates for accessibility
5. Unified Database Infrastructure
- Single credentials file (/private/secrets.php) managing all four
databases:
* u150893401_users - User authentication data
* u150893401_asv_note - Bible content (ASV Bible)
* u150893401_blog - Blog data
* u150893401_familywitness - Legacy family records
- Separate db_config.php files load credentials from the unified
secrets file
- Clear separation: authentication database is completely independent
from Bible content database
- Public Bible access requires no loginβonly user account features
require authentication
6. Email Delivery System
- Built on PHP's native mail() function (no external dependencies)
- HTML and plain-text email bodies for maximum compatibility
- Proper MIME headers for reliable delivery
- Password reset emails confirmed working at 4:49 PM on Oct 4, 2026
SECURITY PHILOSOPHY
The authentication system was designed with these principles:
1. Defense in depth: Multiple layers of protection (rate limiting, CSRF
tokens, secure sessions, audit logs)
2. Password manager compatibility: Proper autocomplete attributes so
credentials are captured correctly
3. No hardcoded secrets: All credentials centralized in
/private/secrets.php
4. Audit trail: Every authentication event logged for forensic analysis
5. Fail securely: Lockouts prevent brute force; token expiry prevents
replay attacks
TECHNICAL STACK
- Hashing: libsodium's sodium_crypto_pwhash_str() with Argon2id
- Tokens: random_bytes() for cryptographic randomness
- Database: PDO prepared statements (no SQL injection vulnerability)
- Sessions: PHP native sessions with secure cookie configuration
- Email: PHP mail() with SMTP-compatible headers
FILES CREATED/MODIFIED
Authentication Infrastructure:
- /private/asv_users/auth.php β AuthService class
- /private/asv_users/tokens.php β TokenService class
- /private/asv_users/session.php β SessionManager class
- /private/asv_users/db_config.php β Database configuration loader
- /private/asv_users/email_v2.php β EmailService class
- /private/secrets.php β Unified credential management
Public Login Pages:
- /public_html/login/register.php β User registration
- /public_html/login/login.php β User login with rate limiting
- /public_html/login/logout.php β Session termination
- /public_html/login/account.php β Profile and password management
- /public_html/login/forgot-password.php β Password reset request
- /public_html/login/reset-password.php β Password reset completion
Database:
- users table with email, password hash, verification status, account
status, login tracking
- password_reset_tokens table with one-time use enforcement
- email_verification_tokens table with expiry
- auth_events table for audit logging
WHAT'S NEXT
The authentication foundation is solid and tested. Next phase: Reading
Plan System
A Scripture reading plan that:
- Adjusts to your pace and preferences
- Displays daily assignments at 4:30 AM with text-to-speech audio
- Tracks missed days and allows catch-up
- Forgives real life (flexible deadline adjustment)
- Reads the Bible to you aloud as you follow along
The authentication system will support user preferences for the reading
plan: duration, literature mix, pacing preferencesβall personalized to
each reader.
LESSONS LEARNED
1. Unified secrets management works well: One file (secrets.php)
managing all database credentials eliminated confusion and errors
2. File naming matters: Clear separation between db_config.php files
for different databases prevents credential mix-ups
3. PHP's built-in mail() is reliable: No need for external SMTP
libraries when native functions work
4. Cache busting required: File renaming (email.php β email_v2.php)
was necessary to clear PHP opcache
5. Shared hosting paths need care: dirname($_SERVER['DOCUMENT_ROOT'])
correctly resolves to /private/ on Hostinger
---
Status: β
Authentication system complete and tested
Ready for: Reading plan implementation
Philosophy maintained: Production-grade, real-world security throughout
2026-05-17 23:18
Clouds Bridge Page / Footnote Routing
Started a new bridge page: clouds.php. Added the PAGE_META block with
page_type set to bridge_pages and title set to Clouds. Verified that the
Study Index reads the metadata correctly and lists Clouds as a bridge page.
Built the opening Scope Statement. Defined the page as a text-gathering node
for passages where cloud language connects with divine presence, divine
movement, heavenly court scenes, judgment, glory, concealment, or figurative
multitude. The page explicitly does not decide which prophetic model is
correct. It maps the passages first.
Added Layer 1 as the active working layer. Kept Layer 0 as the Scripture text
itself, rendered through chapter.php. This preserves the layer architecture:
Scripture first, bridge-page observation second, interpretive taxonomy later,
and argument outside the bridge page.
Built the first Layer 1 study blocks. Added Core Passages for Cloud Language,
Clouds and Divine Presence, Clouds and Divine Movement, Clouds, Glory, and
Concealment, Clouds in Judgment Contexts, Clouds in Heavenly Court Scenes,
Clouds and Divine Speech, Clouds in Son of Man Language, and Cloud as
Figurative Multitude.
Used vlink() throughout the bridge page so every reference points back to the
Scripture layer. Confirmed that the links correctly route from clouds.php to
the exact chapter-and-verse anchors in Layer 0.
Captured an author note on Isaiah 19 while the thought was fresh. The note
records that Isaiah 19:1 names Egypt directly and places cloud-riding language
inside a judgment oracle concerning Egypt. The note was kept out of the
rendered page because it belongs to a future Layer 2 or Layer 3 discussion,
not to the bridge-page map.
Created a new Clouds footnote and linked it to clouds.php. Chose high-signal
entry points instead of attaching a footnote to every cloud occurrence. A full
search found 136 matches for cloud-related language, but the footnote system
should serve as entry ramps, not noise.
Tested the Footnote to Verse Tool with selected passages. Confirmed that the
parser requires the database book name Psalms rather than Psalm. Also confirmed
that verse ranges work when entered with a plain hyphen instead of an en dash.
Attached the Clouds footnote to the selected verses. The preview completed
cleanly with zero unparseable lines, zero unknown book names, and zero missing
verses. Existing attachments were safely ignored.
Verified the full Wissensnetz loop in practice: Study Index to Clouds, Clouds
to Scripture through vlink(), and Scripture back to Clouds through footnotes.
Psalm 104:3 was used as the visible test case. The marker rendered beside the
verse, and the chapter footnote list routed back to the Clouds bridge page.
Result: clouds.php is now a live bridge-page node. It has a working scope
statement, a strong Layer 1 structure, direct Scripture links, and selected
footnote entry points from the Scripture layer. Layer 2 has not been added.
2026-02-13 21:32
Layer Architecture + Day of the Lord Expansion
This round of work focused on structural clarity, not theological
conclusions.
The "Day of the Lord" bridge page moved from simple collection to
layered design.
Layer 0 remains the biblical text itself. No commentary. No framing.
Just Scripture.
Layer 1 now organizes the passages by recipient β Judah, Babylon,
Egypt, Edom, the nations β without collapsing them into a system.
The grouping is observational only.
Layer 2 was added beneath it. This section explains how major
interpretive traditions relate these passages to one another.
The goal was education, not advocacy.
The models are described fairly and in equal weight.
No ranking. No conclusions. No subtle steering.
A formal layer structure has now been defined:
Layer 0 β Biblical text
Layer 1 β Observational organization
Layer 2 β Interpretive taxonomy
Layer 3 β Argument or position essays (not yet implemented)
This architecture allows growth without premature commitment.
Opinion, when it comes, will rest on visible structure.
Visually, layer separation is now handled by simple structural
containers in styles.css.
New classes were introduced: layer_block, layer_1, and layer_2.
Distinction is made by subtle left-border color only.
No background changes. No decorative emphasis.
The beige paper remains the foundation.
The decision was made to explain the layer system once from index.php
rather than cluttering each page with explanation.
Pages remain clean; architecture is documented centrally.
Editorial policy was also clarified.
Future guest authors will write within defined boundaries.
Passion is welcome. Carelessness is not.
All claims must anchor to text.
Strawmen will be returned for revision.
Method is enforced. Conclusions are not pre-approved.
Overall direction this cycle:
More structure.
More discipline.
Clearer reading modes.
No movement into Layer 3.
The fight has not been avoided.
It has been sequenced.
2026-02-09 11:06
Invisible Creatures / Layer 1 Refinement
This update focused on tightening Layer 1 discipline.
The goal was clarity, not expansion.
Several bridge-page sections were flattened.
All <h4> headings were removed where they implied outline logic.
Content was rewritten into report prose.
Headings now stop at <h3>.
All <h3> sections were placed inside .study-block containers.
This made sections visually distinct without adding decoration.
Major <h2> sections remain open and unboxed.
Bullet lists were converted to prose where possible.
This reduced visual emphasis and removed βsummaryβ signals.
Silences are now stated plainly in sentences.
Footnote markers were adjusted for usability.
The markers are slightly larger and easier to click.
No meaning or hierarchy was changed.
A new Devil section was added.
It records New Testament usage only.
Matthew 4:10 was included where Jesus names the Devil as Satan.
No origin story or system was added.
The demons section was expanded with additional texts.
Behavior, speech, belief, limitation, and judgment were documented.
Old Testament Hebrew terms were added as observations only.
The Septuagint connection was noted without drawing conclusions.
Revelation material was kept local to Revelation.
Where Revelation ties identities together, that is recorded there.
Other texts were not forced to follow.
Overall, this pass reduced explanation and increased structure.
The text now speaks more clearly on its own.
Layer 1 stayed descriptive.
2026-02-08 02:27
Footnotes, ordering, and a missing idea
Today exposed a weakness that only shows up once footnotes start
overlapping across verses. When footnotes criss-cross, numbering
based on βfirst seenβ stops being reliable.
The original design handled attachment well (which footnotes belong
to which verses), but it never made chapter-level numbering explicit.
That worked until it didnβt.
The fix was not a patch. It was finishing the model.
A new chapter-level ordering table now defines footnote numbers where
needed. chapter.php obeys that order when it exists and falls back to
the old behavior when it doesnβt.
Zechariah 3 was the test case. Mixed verse markers now display
correctly (1 3, 1 2), while other chapters remain unchanged.