Feature History

2026-08-20 — Notes step out of the nav and out of Recent

The stream keeps every way in except the two that duplicated it.

  1. /notes leaves the primary nav

    • nav: true dropped from its registry entry. That is the only thing it removes: the footer link, the sitemap entry, the notes command and the n key all still work
    • The newest note has a panel above the terminal, which is how a reader finds the stream. A nav slot pointing at the same content is a third route to it on one screen
    • Nav is back to three: Projects, Posts, Fanfic
  2. Notes leave Recent

    • RecentSection filters type !== 'NOTE'
    • LatestNote prints the newest note at the top of the same page, so a note row in Recent is the one entry guaranteed to be a repeat — and a burst of notes would push every project, post and story off a five-row list
    • /feed, /search and /tags still carry them; only the homepage’s five-row summary does not

2026-08-20 — The newest note, above the terminal

The homepage notes block moved above the hero and shrank to one note.

  1. NotesSection → LatestNote

    • Two notes below the terminal became one note above it. The newest note is the only thing on the homepage that changes weekly, so it is the only thing that earns a place ahead of the hero
    • One, not two: a stack pushes the terminal off the first screen, and the terminal is the site’s opening move
    • Order in <main> is now the hidden h1, the note panel, the terminal
  2. Shaped like a tweet, not styled like one

    • Handle, timestamp, body, quiet footer — the form the content is written in
    • The timestamp is the permalink, the way it is on a tweet
    • No avatar, no fill, no pill, no counts, no colour. Hierarchy is case and tone, as everywhere else
    • An unfilled --border-color box against the terminal’s --bg-subtle fill directly below, so the two read as siblings and the panel is the quieter
  3. Fixed a one-space indent on every note

    • .note-body is white-space: pre-wrap so a note can carry deliberate line breaks. That also means Astro’s template whitespace around the expression renders, which was indenting every note by a space
    • NoteBody.astro now closes its <p> tags tight against the expression. One fix, three surfaces: the panel, /notes, and the permalink page
  4. The footer stopped needing an empty element

    • justify-content: space-between plus a placeholder <span /> became margin-left: auto on the link, since the tag list is optional and the link has to sit right whether or not it is there

2026-08-20 — Two lengths of writing

The diary became posts, and a second, shorter collection joined it. One place for pieces worth a title, one for the things that are not.

  1. diary → posts

    • The rename happened while the collection held a single entry: the cost was a registry line and one RSS URL nobody had subscribed to. In six months it would have been a redirect table
    • /diary → /posts, /diary/:slug → /posts/:slug, /diary/rss.xml → /posts/rss.xml. DiarySection → PostsSection, EntryType.DIARY → POST
    • The keyboard shortcut moved from d to w — p was already projects, and the two are near enough that a wrong key would land on the wrong page
    • Names were picked as a pair, for contrast: notes is short, posts is long. “Diary” alongside “notes” would have said personal-and-dated twice and long-form nowhere
  2. /notes — the short-form stream

    • A note is a body and a date. No title, no file
    • It is the one collection that is not a folder of markdown: notes.yaml holds a single array read by the file() loader, so posting is appending three lines rather than creating, naming and front-mattering a file to publish one sentence — and the filename would have been doing nothing anyway, with no title to derive it from
    • The trade is that file() entries have no body to render(). Note bodies are plain text; linkify() splits them into text and link runs so NoteBody.astro can render real anchors without ever handing a string to set:html
    • /notes/:id exists even though the whole stream fits on one page — /search, /feed and RSS all need something to link to, and a feed reader dedupes by link, so a shared URL would collapse the stream into one item
  3. Titleless entries got a single fallback, not four

    • SiteEntry.title is now optional, and entryTitle() returns the title where there is one and the head of the body where there is not, cut on a word boundary
    • RecentSection, /feed, /search and both RSS feeds call it. The truncation rule lives in one place rather than being reinvented per surface
    • CardItem.title stays required — notes do not render through CollectionList, and an optional title there is flexibility nothing uses
  4. Notes are kept out of /feed.xml

    • Short-form posting is bursty. A day of five notes would bury a long post in every reader subscribed site-wide, so notes are syndicated separately at /notes/rss.xml and subscribing to the churn is opt-in
    • The /feed page still lists them: burying costs a reader’s unread count, not a page you scroll, and that page promises everything
  5. The homepage grew a notes block, above Recent

    • Two notes, rendered in full — at two sentences each, a card would be more chrome than text
    • Deliberately outside the section-reordering block below it. That block’s job is to signal someone is actively here, which only works if it sits in the same place every time
    • The terminal hero is untouched and still first
  6. Both note permalinks carry an explicit :hover

    • Astro scopes .note-date to (0,2,0), which outranks the global a:hover at (0,1,1). Without a matching rule the date would keep its muted colour on an inverted background — invisible, not merely ugly. Third variant of the hazard already documented in CLAUDE.md
  7. /tags links notes without a ?tag=

    • /notes has no filter UI — the stream is one page and Ctrl-F is the search
    • A ?tag= there would be a parameter nothing reads, so FILTERABLE in tags.astro names the three pages that actually honour it

Nav is now four of its five slots: Projects, Posts, Notes, Fanfic. The build is 19 pages, up from 17.

2026-08-20 — One rhythm for every list

Adding the Recent list put four different list treatments on one page. They now share a single row rhythm and a single column geometry.

  1. The type column right-aligns against a fixed floor

    • date [type] title sized its type column to the longest type on the page, so [diary] left two blank characters before its title while [project] sat snug — HELLO WORLD looked pushed away from its own bracket
    • justify-self: end plus min-width: var(--col-type) ends every ] exactly one --space-3 before its title, whatever the type name is
    • --col-type is in ch, and a longer type name simply widens the column rather than breaking the alignment, so new collections need no change
    • /feed gets a 7ch floor on its date for the same reason: each year is a separate grid, and without it the titles stepped in and out between years
  2. Every list row is padding: var(--space-4) 0

    • .card was --space-5 and .recent-item was --space-3; /search was already on --space-4, and the others now match it
    • .recent-list gains the border-top hairline .grid and .search-results already had, so all four lists are framed the same way
  3. .view-all and the section heading moved to global.css

    • The three homepage sections each carried an identical scoped .view-all (centred, hardcoded 2rem) and an identical inline style on their h2
    • The Recent list closes with the same .btn the sections do, left-aligned like everything else in a one-column layout
  4. Fanfic’s homepage section caught up with the others

    • It sliced the raw collection, which is filesystem order — the “latest” three chapters were whatever the loader returned first. Now sorted by date
    • Its cards had no date line; they now print one like Projects and Diary
    • Its series label used an inline margin-bottom: 0.75rem instead of the existing .card-eyebrow class

2026-08-20 — The hero is one line now

The terminal opened with roughly twenty-four lines of pre-baked output — whoami, cat about.txt, ls skills/, ls sections/, and a five-item git log — above three macOS window dots. It spent the entire first screen restating what the sections below already said, and pushed the actual content off the fold.

  1. The hero collapsed to a single prompt row

    • One $, one input, one muted hint. Nothing is printed until you ask for it
    • Command output is capped at 40vh and scrolls, so help or ls can no longer grow the hero without limit
    • The window dots are gone from the hero and from /404. They were the one skeuomorphic element on a site whose rule is hairlines and square corners, and .terminal-header / .terminal-dot were deleted from global.css
  2. The static copy became commands

    • whoami now carries the tagline and the about paragraph
    • skills is a new built-in listing what the work is in
    • ls sections/ was redundant with ls, which already lists every page
    • Nothing new shadows a route: about stays the /about command, since built-ins win on a name collision and would have hidden the page
  3. Recent updates moved out to RecentSection.astro

    • The git log --oneline -5 block is now real markup on the homepage, so it renders without JavaScript and costs the hero nothing
    • It reads the same allEntries() that /feed and /search do
    • Dates are ISO here, unlike /feed: this list is ungrouped and spans years, so a bare Apr 7 would be ambiguous
  4. The hand-positioned cursor is gone

    • The old fake caret was absolutely positioned from inputField.value.length in ch units, updated on every keystroke, and drifted whenever the field scrolled. The browser’s own caret now does the job while the field has focus
    • A blinking block still marks the resting state, shown only while the field is both empty and unfocused, so the two can never appear at once
    • The block is centred with top: 0; bottom: 0; margin-block: auto rather than a tuned top offset. Absolute positioning drops the vertical-align: text-bottom it inherits from .terminal-cursor, and the first attempt at top: 0.1em left it riding ~3px high in the input’s 1.7 line-height box. Centring tracks the real caret and survives a change to the line-height or the step size; a magic offset does not
  5. The tab no longer says what the site is

    • The homepage title was Developer Portfolio | SSani, which described the genre rather than the site. It is now just SSani
    • title became optional on BaseHead and Layout so the homepage can omit it. PageShell and CollectionPage still require it, which keeps the compile-time guard where most pages actually enter the chain
    • The same filler was in three other places a reader would meet it: the default meta description, og:site_name (SSani Portfolio), and the homepage’s visually-hidden h1. grep -i portfolio over src/ is now empty

2026-08-20 — Quiet Terminal: a full design system replacement

The monochrome soft-brutalist system was replaced outright. It was built on devices — 3px opaque borders, 6px/10px hard drop shadows, translate(-4px, -4px) hover lifts, pure #000000/#f4f4f9 — that the reference sites for this pass (nelson.cloud, carteakey.dev) deliberately avoid. Softening them one at a time would have left the old rules fighting the new look, so the doctrine was rewritten rather than patched.

  1. One neutral ramp replaces the five-colour palette

    • The blue-grey five (#000000, #2f4550, #586f7c, #b8dbd9, #f4f4f9) became a twelve-step cool grey ramp, --n-0 through --n-950, carrying a trace of the old hue so the site still reads as itself
    • Neither end is pure: the light page is #e4e7ea and the dark page is #0d0f11. A page should read as paper with ink on it, not as a light box
    • Body text clears 4.5:1 in both themes and control borders clear 3:1. A new --rule token carries the decorative hairlines, which do not clear 3:1 and must never be used on a control
  2. The two accent families collapsed into one

    • .terminal used to be pure black in both themes, which forced a parallel set of --terminal-* accents that could survive on black, and a standing warning never to use a page token inside it
    • The terminal now follows the theme like everything else, so that whole second family is gone. --accent-color, --warning-color, --success-color, --error-color and --muted-color are deleted — they had no consumers
    • The --terminal-* names survive only as aliases onto page tokens, because TerminalHero.astro writes them at runtime onto elements it creates itself
  3. A flat type scale in one monospace family

    • Quicksand and Nunito were replaced by Cascadia Mono for headings, body, UI and code alike — the ligature-free cut of the Windows Terminal face, chosen over JetBrains Mono, Intel One Mono, IBM Plex Mono, Geist Mono and Space Mono after comparing all six at the site’s real sizes with real content. Cascadia Code is the same design with code ligatures and must not be substituted: they would fire inside prose
    • h1 through h6 are now the same 0.875rem as body text. A heading is marked by uppercase, weight 700 and letter-spacing, and in .prose by a literal ## printed via ::before
    • The fluid clamp() heading scale is gone, along with every scoped font-size on a heading in 404.astro, diary/[slug].astro, fanfic/[slug].astro, feed.astro, site-health.astro and PageShell.astro
    • .metric-value on /site-health keeps 1.5rem as the one exception: a metric is a quantity being read as a number, not a heading
  4. Hairlines, square corners, and nothing that moves

    • --border-width 3px → 1px; --border-radius 4px → 0
    • All four shadow tokens are retired to none, kept defined so a stale reference resolves to nothing rather than to an invalid value. One hard shadow survives, on the shortcuts dialog, the only surface that floats
    • Every transform hover lift was removed and every transition: all replaced with an explicit colour-property list. Hover now inverts foreground and background instead. This is load-bearing: when hover moved an element, keyboard focus had no distinct cue left, so the :focus-visible ring is now the only thing that means focus
    • .badge-new lost its pulsing scale animation and became a static inverted block
  5. One column, and lists instead of a card grid

    • .container went from max-width: 1200px to --measure 80ch, applied to nav, main and footer alike
    • .grid is flex-direction: column at every width and .card is a row with a hairline underneath — no border, no fill, no shadow. Class names are unchanged because every list page and homepage section renders through them
    • .tag renders [like-this] via ::before/::after instead of as a chip
  6. Fallout in TerminalHero.astro, caught by the post-change sweep

    • getTypeColor() mapped PROJECT/DIARY/FANFIC onto --terminal-accent / --terminal-success / --terminal-warning. Under the new palette those resolve to just two values, so it rendered a three-way code as two indistinguishable groups with DIARY arbitrarily dimmed. Deleted; the [TYPE] text prefix was always the real differentiator, and the label now renders like every other bracketed label — muted, weight 400
    • That label also carried an inline font-weight: 600. Cascadia Mono does offer a 600 on Google Fonts, but this site loads only 400 and 700, so the browser was synthesising a fake semibold — the same fault the old Quicksand 900 had
    • Links inside the terminal went invisible on hover. The new a:hover inverts the background, but an inline style="color:…" outranks it, so the text kept its normal colour on an inverted ground: 1.00:1 in light, 1.27:1 in dark. Fixed by removing the inline colours and letting the elements inherit; the dotted underline now uses currentColor
    • The same fault existed in the stylesheet: .check-item a set color: var(--text-primary), tying a:hover at (0,1,1) and winning on source order. The declaration only duplicated the base a rule, so it was deleted rather than patched
  7. Documentation caught up with the change

    • CLAUDE.md’s design system and key principles were rewritten; the old five-colour and soft-brutalist rules are gone rather than amended
    • /colophon and /uses described the retired system in prose and were rewritten
    • README.md’s design section was replaced. Earlier entries in this changelog still describe the old system, correctly, as history

2026-08-20 — Route Registry, Keyboard Shortcuts, Search, Tags and Feed

A structural pass, prompted by looking at what a fuller personal site looks like (see ROADMAP.md). The theme throughout: one source of truth per concern, so the site can grow without every addition being copied into five places.

  1. Deduplicated the list pages

    • projects.astro, diary.astro and fanfic.astro were the same page written three times, and ProjectFilters / ContentFilters were one component wearing two hats. They became CollectionPage, CollectionList, CollectionFilters and a CardItem type in src/lib/cards.ts
    • 676 lines across 5 files became 496 across 7, and a new list page now costs about 30 lines of mapping with no markup and no CSS
  2. A route registry

    • src/config/routes.ts is now the single source for the nav, the footer, the 404 suggestions, the sitemap filter, the terminal’s commands and the keyboard shortcuts. A page missing from it is invisible to all of them
    • status: 'draft' and unlisted: true keep a route out of every listing and out of the sitemap while it still builds and stays reachable
    • The nav now points at the list pages. It previously linked to homepage anchors, so “Projects” and /projects were different destinations sharing a label
  3. The terminal is generated, not hardcoded

    • help used to be a fixed HTML string listing three commands while the switch handled five. Commands now derive from the registry: 12 instead of 5, and a new page is typeable the moment it is registered. ls prints every page
  4. Keyboard shortcuts

    • ? opens an overlay rendered from the same registry, m toggles the theme, and each route can claim a key — currently h p d f e s t c
    • Every shortcut is suppressed while typing in a field, so typing help in the terminal does not fire four navigations
  5. New pages — /search (server-rendered, works without JavaScript), /tags (with ?tag= deep links into the list pages), /feed (everything by year), /colophon, /changelog, and drafts for /now, /now/archive, /about and /uses

  6. A site-wide RSS feed at /feed.xml, listed first for autodiscovery. Projects have no detail page, so entries now anchor to their card (/projects#jualbagasi) — a bare /projects gave every project the same link, and feed readers dedupe by link

  7. The kanban board was removed — page, collection, schema, five cards and its terminal command. Its one unrecorded plan, fanfic reader controls, was carried into ROADMAP.md

  8. Corrections found by testing the running site

    • Every page title said the brand twice: SSani | Dev Diary | SSani
    • robots.txt pointed crawlers at a GitHub Pages domain the site does not use
    • The OpenGraph image was a 404, so every link preview was broken. There is now a real 1200×630 image built from the palette
    • /site-health claimed a skip link that did not exist. There is now a skip link
    • The homepage and 404 had no h1; diary entries rendered their title twice. Every page now has exactly one
    • .search-input:focus set outline: none, overriding the site-wide focus ring with a cue identical to hover
  9. Shared page chrome — the same 20-line header and back-nav block had been copied into ten files. PageShell now owns it; site-health keeps its own, deliberately. Markdown styling likewise collapsed into one .prose class

Verified throughout by diffing the rendered text of every built page against the previous build, so the refactors are provably not visual changes. Builds 17 pages.

2026-08-13 — New Five-Colour Palette

The grey + blue/orange/green/red system was replaced with a five-colour blue-grey palette: #000000 black, #2f4550 charcoal-blue, #586f7c blue-slate, #b8dbd9 light-blue, #f4f4f9 ghost-white. The five are now raw tokens in :root and are the only colours in the system.

  1. Both theme blocks rewritten

    • Light: background #f4f4f9, subtle #b8dbd9, text #000000, secondary text #2f4550, borders and hard shadows #000000
    • Dark: background #000000, subtle #2f4550, text #f4f4f9, secondary text #b8dbd9, borders and hard shadows #f4f4f9
    • --bg-subtle is a real tint now rather than a near-invisible grey step, so filter panels, the site-health block and kanban columns read as distinct surfaces
  2. The terminal is #000000 in both themes

    • Previously #1A1A1A light / #0A0A0A dark. A single value means terminal accents only ever have to clear one background
    • --terminal-text-muted moved from #A0A0A0 to #586f7c (5.0:1)
  3. Accent tokens now mark rank, not hue

    • A single hue family cannot carry red-means-danger. --warning-color is the loudest tone (#000000 light / #f4f4f9 dark), --success-color the mid tone (#586f7c), and a new --muted-color (#586f7c) the quietest
    • Kanban’s on hold column moved from --text-secondary to --muted-color. Only three tones clear each column background, so on hold and done now share #586f7c. They sit at opposite ends of the board and each column is labelled in text
    • Terminal accent, warning and success all resolve to #b8dbd9 for the same reason; terminal error takes #f4f4f9
  4. Legacy hex values removed everywhere

    • theme-color metas in BaseHead.astro are #f4f4f9 / #000000
    • var(--token, #fallback) fallbacks in kanban.astro and TerminalHero.astro updated to palette values
    • The modal backdrop scrim rgba(0, 0, 0, 0.7) was kept — it is a scrim, not a palette colour

Contrast was recomputed rather than assumed, then checked in a headless browser against the computed styles. Everything meets 4.5:1 on the background it actually renders on. The thinnest margin is #586f7c on --bg-primary at 4.8:1 light / 5.0:1 dark, used for the ON HOLD and DONE column headings — which sit on .kanban-column-header’s --bg-primary, not on the tinted column body. Build passes: 9 pages.

2026-08-13 — Emoji Removed from UI

Three colour emoji were the only pictographs in a monochrome system. Removed for minimalism. The typographic glyphs →, ←, ↑ and ✓ were deliberately kept — they are plain text characters that take the current text colour and carry meaning.

  1. Theme Toggle Is Now a Text Label

    • 🌙 / ☀️ replaced with two spans, .theme-label-dark (“Dark”) and .theme-label-light (“Light”)
    • The swap is pure CSS, keyed off [data-theme] on <html>. The old updateButtonIcon() JS helper and both its call sites were deleted
    • CSS rather than JS because the inline <head> script sets data-theme before first paint. Setting the label from the deferred module script instead would briefly show the wrong word on a dark-mode load
    • .theme-toggle picked up text-transform: uppercase, letter-spacing: 0.05em and font-weight: 600 to match the nav links
    • min-width: 5.5rem (88px) stops the nav jumping between the 4-character “Dark” and 5-character “Light”. An initial 4.5rem was too small to bind — measured content widths are 79.9px and 82.5px, so the button still shifted 2.6px until the floor was raised
  2. Search Icon Removed

    • Dropped the decorative 🔍 span from ProjectFilters.astro and ContentFilters.astro. It was aria-hidden="true", and both inputs keep their placeholder and aria-describedby help text
    • Deleted the now-unused .search-icon rule
    • .search-input right padding went from 2.5rem back to 1rem, since that space existed only to clear the icon

Verified in headless Chrome, both themes: the label swaps correctly in both directions on a real click, button width is a constant 88px across three reads, .nav-brand and .nav-links positions are unchanged between themes, and a DOM sweep for pictograph code points found none.

2026-08-12 — Design Restraint Pass, Accent Tokens & Contrast Fixes

Design: Restraint Pass

The soft-brutalist devices (3px border + hard shadow) were applied to every element, including things you only read. They now mark interactive elements only.

  1. Flattened reading surfaces

    • Card tags: .tag, .project-tag, .content-tag were three near-identical rules; merged into one grouped selector. Now quiet uppercase letterspaced text in --text-secondary, no border or background
    • .metric: dropped border and background tint; numeral plus label only
    • .health-card: dropped border, shadow and hover transform (it is not clickable). Removes one of three nested border levels on /site-health
    • Panels .site-health, .project-filters, .content-filters, .active-filters: dropped 3px border and hard shadow, kept the --bg-subtle tint
    • Kept border + shadow on .btn, .card, .terminal, .search-input, .theme-toggle, .social-link, .scroll-to-top, .tag-filter, .badge-new, .section-label
  2. Unified Hover Lift

    • Was four different values (1px, 2px, 4px) across three shadow sizes
    • Now two tiers: controls lift 2px (--shadow-small 4px → 6px), cards lift 4px (--shadow-standard 6px → 10px)
    • Added :active return-to-rest on .theme-toggle and .scroll-to-top, matching .btn
  3. Calmer Link Hover

    • Base a:hover inverted the full background on every link, including prose inside diary and fanfic entries
    • Now thickens the underline. .nav-link and .nav-brand got explicit hover rules to keep the inversion, since they previously relied on the base rule
  4. Fluid Type Scale

    • h1/h2/h3 now use clamp() and scale with viewport width
    • Removed the redundant heading overrides in the max-width: 768px block, and the scoped font-size: 3rem on .page-header h1 in diary.astro, fanfic.astro and projects.astro that was blocking the scale
  5. Design Tokens Added

    • Spacing scale --space-1 (0.25rem) through --space-8 (4rem)
    • --border-hairline, --shadow-small, --shadow-small-hover, --font-mono

Accent Color Tokens

--accent-color, --warning-color, --success-color and --error-color were referenced across the project but defined nowhere. Where an inline fallback existed, a color appeared; where it did not, the declaration was invalid and the text silently inherited white.

  1. Two Families, Because There Are Two Backgrounds

    • The page background flips with the theme; .terminal is always dark. One family cannot serve both — a light-mode-safe dark blue is unreadable on the terminal, and a bright blue is 2.1:1 on the light page
    • Page accents (--accent-color, --warning-color, --success-color, --error-color) have different values per theme. Light: #1565C0, #B45309, #2E7D32, #C62828. Dark: #64B5F6, #FFB74D, #81C784, #EF5350. All ≥ 4.8:1
    • Terminal accents (--terminal-accent, --terminal-warning, --terminal-success, --terminal-error) hold the same bright value in both themes. All ≥ 5.1:1 on both terminal backgrounds
    • Added --terminal-text-muted #A0A0A0 (6.8:1 on #1A1A1A, 7.6:1 on #0A0A0A)
  2. Usages Repointed and Fallbacks Removed

    • Terminal-context sites (getTypeColor() in TerminalHero.astro, the help command output, the 404 error line, .terminal-prompt) now use --terminal-*
    • Page-context sites (getStatusColor() and .modal-body in kanban.astro) use the page names
    • Deleted every inline var(--x, #fallback) for these names — the variables are now always defined, so the fallbacks were a second source of truth
    • .terminal-prompt was the last hardcoded hex (#4CAF50) outside the theme blocks

Accessibility & Contrast Fixes

  1. Undefined Variables Broke Terminal Colors

    • --text-color, --text-muted and --card-bg were used with no fallback in TerminalHero.astro, 404.astro and kanban.astro. An undefined custom property with no fallback makes the declaration invalid, so color inherited white from .terminal — correct only by accident
    • All now point at the right token for the background they render on
  2. Seven #666 Values Replaced

    • .card-meta plus .page-description on three list pages and four rules across the two [slug] detail pages
    • #666 on the dark background is 3.1:1, under the 4.5:1 minimum. Now --text-secondary
  3. Invisible Code Blocks in Dark Mode

    • <pre> and <code> in diary and fanfic entries had a fixed #f5f5f5 background. In dark mode that rendered near-white text on near-white. Now --bg-subtle
  4. Missing Keyboard Focus Ring

    • .search-input:focus set outline: none and no :focus-visible rule existed anywhere. Tabbing showed nothing
    • Added a site-wide :focus-visible outline for links, buttons, inputs and [tabindex]
  5. Faux-Bold Headings

    • h1–h6, .badge-new, .nav-brand and kanban’s .page-title used font-weight: 900. Only Quicksand 400/600/700 are loaded and Quicksand has no 900 weight, so the browser synthesised it. All now 700
  6. Terminal Header Divider

    • .terminal-header border used --bg-subtle, which is #2A2A2A in dark mode — 1.4:1 on the #0A0A0A terminal, effectively invisible. Now --terminal-text-muted
  7. 404 Buttons Styled for the Wrong Background

    • .btn-secondary sits inside the terminal but used page tokens. Border and shadow were #2A2A2A on a dark terminal in light mode. Now terminal tokens throughout, with an inverted hover
  8. Stray Chroma Removed

    • #22c55e on .metric-value.high and .check-icon replaced with --text-primary

Security

  1. Escaped Terminal Input
    • The hero terminal echoed the typed command into innerHTML unescaped at two call sites, so typed HTML was parsed
    • Self-XSS only — input comes from the visitor’s own keyboard on a static page, with no persistence and no URL parameter, so it could not affect another visitor. Fixed regardless
    • Added an escapeHtml() helper applied to the echoed command and the “command not found” message. addHistoryLine() still accepts HTML, because every other caller passes intentional trusted markup

2026-08-12 — Documentation Accuracy & Deployment Fix

Documentation Updates

  1. LICENSE File Added

    • Added MIT license file with Copyright (c) 2026 SSani
    • README badge now correctly links to existing ./LICENSE
  2. Homepage Screenshot Added

    • Added screenshot.png (1440×1250) — real capture of homepage in dark mode from production preview build
    • README image reference now renders instead of broken image
  3. Corrected Responsive Breakpoints Claim

    • Updated from “480px and 768px” to “480px, 768px and 1024px”
    • Matches actual media queries in src/styles/global.css
  4. Corrected Footer Social Links Claim

    • Updated from “GitHub, Twitter, Email” to “GitHub, Email”
    • src/layouts/Layout.astro has no Twitter link; Twitter Cards SEO meta tags remain in BaseHead.astro
  5. Fixed Project Structure Tree

    • Added kanban.astro, src/pages/diary/rss.xml.ts, and src/pages/fanfic/rss.xml.ts
    • Merged duplicated src/content/ node
    • Added CLAUDE.md and CHANGELOG.md as top-level entries
  6. Updated Documentation Table

    • Replaced stale references to deleted context.md and four .windsurf/ files (removed in commits 2288d24 and 34a4125)
    • Now lists CLAUDE.md and CHANGELOG.md

Deployment & Configuration

  1. Unified Deployment Target to Coolify

    • Updated CLAUDE.md Deployment section from “Netlify, Vercel, or similar” to Coolify
    • Removed “Deploy to hosting” from Possible Future Work (deployment is completed)
    • Moved src/content/kanban/publish-portfolio.md from status “plan” to “done”
  2. Renamed Package Name

    • Updated package.json “name” field from “developer-portfolio” to “ssani-homepage”

Build verified: 9 pages generated, no errors.


2026-07-07 — Bug Fixes & Astro 5 Content Layer Migration

Fixes

  1. Terminal Recent Updates Links

    • Fixed 404 errors in TerminalHero’s recent-updates feed for PROJECT and KANBAN items
    • Added getEntryHref() helper to correctly map entry types to their URLs: PROJECT → /projects, KANBAN → /kanban, DIARY → /diary/:slug, FANFIC → /fanfic/:slug
    • Previously generated non-existent /project/<slug> and /kanban/<slug> routes
  2. Fanfic RSS Feed Dates

    • Fixed fanfic RSS feed using build-time new Date() instead of actual content dates
    • Now sorts by and emits the real date field from fanfic frontmatter for pubDate
    • Removed stale comment claiming no date field existed in schema; newest entries appear first in feed
  3. Canonical Domain Unification

    • Updated both diary/rss.xml.ts and fanfic/rss.xml.ts to use unified canonical domain https://ssani.dev
    • Changed RSS site fallback from https://ssani-main.github.io to match astro.config.mjs

Migration

  1. Astro 5 Content Layer

    • Migrated all four content collections to Astro 5 Content Layer API
    • Updated src/content/config.ts to use glob() loader from astro/loaders
    • Swapped all entry.slug references to entry.id across components
    • Updated kanban.astro to use render(entry) instead of entry.render()
    • Verified all routes unchanged: /diary/hello-world/, /fanfic/harunosukuna-ch1/, etc.
  2. SiteHealth Component Relabel

    • Relabeled hardcoded “Lighthouse Scores” card to “Lighthouse Targets”
    • Reworded subtitle to clarify these are aspirational targets, not live measured metrics
    • Prevents confusion with real performance monitoring

2026-02-25 — Interactive Terminal Hero & Kanban Board

  1. Interactive Terminal Hero

    • Upgraded the static terminal in TerminalHero.astro to be fully interactive
    • Added a faux-input cursor and input field
    • Users can type help to see commands, clear to hide all initial outputs, and kanban to navigate to the hidden route
  2. Kanban Board

    • Created a hidden /kanban route to track work progress without cluttering the main navigation
    • Added a kanban content schema with status (on hold, plan, work, done)
    • Grouped statuses into a 4-column brutalist responsive layout
    • Added a clickable modal overlay to view the full markdown body of each card

2024-02-16 — Dynamic Homepage “NEW” Badge

  1. “NEW” Badge Indicator
    • Added isNew prop to topmost dynamically sorted section on index.astro
    • Added conditional badge snippet to ProjectsSection.astro, DiarySection.astro, and FanficSection.astro
    • Added pulsing soft-brutalist custom styling for .badge-new to global.css

2024-02-16 — Dynamic Section Ordering

  1. Dynamic Homepage Sections
    • Queries all three content collections (projects, diary, fanfic)
    • Determines the most recent update date for each collection
    • Automatically reorders sections on homepage based on recency
    • Most recently updated collection appears first
    • Updates instantly when new content is added

2024-02-16 — Custom 404 Page

  1. Custom 404 Page
    • Terminal-style error page matching site aesthetic
    • Displays find command with the requested URL showing “ERROR: File not found”
    • Shows large “404” error code in terminal format
    • Navigation buttons: Home, Projects, Diary, Fanfic
    • Styled with brutalist design system (hard shadows, borders)
    • Responsive layout with centered terminal card

2024-02-16 — Terminal Hero Recent Updates

  1. TerminalHero Component Enhanced

    • Queries all content collections (projects, diary, fanfic) at build time
    • Displays latest 5 updates in terminal-style git log --oneline format
    • Shows content type with color coding: PROJECT (blue), DIARY (green), FANFIC (orange)
    • Relative timestamps: “today”, “yesterday”, “X days/weeks/months ago”
    • Clickable links to individual content pages
    • Gracefully handles long titles with truncation
  2. Fanfic Schema Update

    • Added required date field to fanfic collection schema
    • Updated all fanfic content files with dates:
      • debug-run.md: 2024-02-10
      • silicon-souls-ch1.md: 2024-02-14
      • silicon-souls-ch2.md: 2024-02-15

2024-02-16 — Tags, Search & Filtering for All Content

  1. Content Schema Updates

    • Added tags: string[] (optional) to Projects collection
    • Added tags: string[] (optional) to Diary collection
    • Added tags: string[] (optional) to Fanfic collection
    • Updated src/content/config.ts with new schema fields
  2. ProjectFilters Component

    • Real-time search by title, description, or tags
    • Tag filter buttons (click to toggle)
    • Active filters display with individual remove buttons
    • “Clear all” functionality
    • Results count indicator
    • Client-side JavaScript filtering
  3. ContentFilters Component

    • Reusable component for Diary and Fanfic
    • Same search and tag filtering capabilities
    • Dynamic content type support
  4. Content Updated with Tags

    • Projects: API Gateway (Node.js, API, JWT), Code Sync (TypeScript, React), TaskFlow (PWA), API Gateway Pro (GraphQL)
    • Diary: Legacy Systems (PostgreSQL, DevOps), Terminal Fonts (Typography), Why Astro (Performance)
    • Fanfic: Debug Run (Sci-Fi), Silicon Souls (AI, Series)
  5. List Pages Enhanced

    • projects.astro — Added ProjectFilters, tag display on cards
    • diary.astro — Added ContentFilters, tag display on cards
    • fanfic.astro — Added ContentFilters, tag display on cards
    • All cards have data-* attributes for client-side filtering
  6. CSS Updates

    • .project-filters and .content-filters styles
    • .tag-filter button styles with hover/active states
    • .search-input with brutalist styling
    • .active-filters display styling
    • .project-tag and .content-tag card tag styling

2024-02-16 — List Pages and Detail Pages for Diary/Fanfic

  1. List Pages (Show All Entries)

    • /projects — src/pages/projects.astro — Shows all projects in grid
    • /diary — src/pages/diary.astro — Shows all diary entries in grid
    • /fanfic — src/pages/fanfic.astro — Shows all fanfiction stories in grid
    • Consistent page header styling with title and description
    • Back to home navigation on all list pages
    • Brutalist styling with 3-column responsive grids
  2. Detail Pages (Individual Entries)

    • Diary detail: src/pages/diary/[slug].astro — Full blog-style pages for each diary entry
    • Fanfic detail: src/pages/fanfic/[slug].astro — Full story pages for each fanfic entry
    • Uses Astro’s getStaticPaths() for dynamic route generation
    • Renders markdown content with proper typography styling
    • Navigation: Back to list + Home links
    • Styled content: code blocks, blockquotes, lists, horizontal rules
  3. Homepage Section Updates

    • All sections now show max 3 items on homepage
    • Added “View All” buttons when more than 3 entries exist:
      • “View All Projects →” linking to /projects
      • “View All Entries →” linking to /diary
      • “View All Stories →” linking to /fanfic
    • Consistent styling with centered buttons
  4. Routes Generated (10 pages total)

    • / (home)
    • /projects (all projects)
    • /diary (all diary entries)
    • /diary/:slug (3 individual entries)
    • /fanfic (all fanfiction)
    • /fanfic/:slug (3 individual stories)

2024-02-16 — Initial Portfolio Setup

  1. Astro Project Structure

    • Full Astro configuration with TypeScript
    • Content collections enabled with Zod schemas
    • Static site generation setup
  2. Monochrome Soft-Brutalist Design System

    • Pure CSS implementation (no Tailwind)
    • Custom CSS variables for consistency
    • Hard shadows (6px/10px offsets)
    • 3px borders with 4px radius
    • Typography: Quicksand (headings) + Nunito (body)
  3. Terminal-Style Hero Section

    • Dark background terminal window
    • Simulated command-line interface
    • Blinking cursor animation
    • Terminal dots (minimize/maximize/close style)
    • Typewriter-style prompt aesthetic
  4. Content Collections

    • projects/ — Portfolio projects with GitHub/live URLs
    • diary/ — Development diary entries with excerpts
    • fanfic/ — Creative writing with series support
    • Zod schemas for type-safe frontmatter
  5. Section Components

    • Projects section with 3-column responsive grid
    • Diary section with date formatting
    • Fanfic section with series/chapter support
    • Section separators with centered labels
  6. Card Components

    • Hard shadow cards with hover lift effect
    • Color inversion on hover (-4px, -4px translate)
    • Consistent border and shadow styling
    • Button inversion hover states
  7. Sample Content

    • 3 projects: TaskFlow Pro, API Gateway, Code Sync
    • 3 diary entries: Astro migration, terminal fonts, legacy systems
    • 3 fanfic stories: Silicon Souls chapters 1-2, Debug Run

Resolved Issues

2024-02-16 — Content Schema Validation Error

Symptom: Build failed with error: Invalid content entry for "fanfic" collection. ["chapterNumber"]: Expected type "number", received "null"

Root Cause: In debug-run.md, optional fields chapterNumber and seriesTitle were explicitly set to null. The Zod schema uses .optional() which expects either the correct type or the field to be omitted entirely — not null.

Solution: Removed the chapterNumber: null and seriesTitle: null lines from the frontmatter. Optional fields should be omitted, not set to null.

Files Affected:

Prevention: When a schema marks a field as .optional(), either:

  1. Provide a valid value of the specified type
  2. Omit the field entirely Never set optional fields to null unless the schema explicitly allows it with .nullable()

Symptom: Clicking PROJECT or KANBAN items in the terminal hero’s “git log” feed resulted in 404 errors (non-existent URLs).

Root Cause: The TerminalHero.astro component built hrefs as /${type.toLowerCase()}/${slug}, assuming every content type has a /type/:slug detail route. However, projects and kanban have no individual detail pages — they only have list routes (/projects, /kanban), not per-entry pages.

Solution: Added a getEntryHref() helper function in TerminalHero.astro that maps each entry type to its correct URL: PROJECT → /projects, KANBAN → /kanban, DIARY → /diary/:slug, FANFIC → /fanfic/:slug.

Files Affected:

Prevention: When generating links to content entries, always check if the collection has individual detail pages before creating /:slug links. Only generate detail-page links for collections that have a [slug] route.


2026-07-07 — Fanfic RSS Feed Used Build-Time Dates

Symptom: Every fanfic RSS item had an identical pubDate that shifted with each build (matched the build timestamp, not content dates).

Root Cause: The fanfic/rss.xml.ts feed builder used pubDate: new Date() for all items and sorted by slug instead of date, ignoring the date field defined in the fanfic schema. A stale code comment incorrectly claimed no date field existed.

Solution: Updated feed generation to sort by and emit the actual fanfic.data.date field for each item’s pubDate, ensuring stable and accurate publication dates with newest entries first.

Files Affected:

Prevention: Always map RSS pubDate to the content’s actual date field (from frontmatter). Never use new Date() or build-time timestamps for feed publication dates.