47 Commits
Author SHA1 Message Date
biosias 2f1bb8acfe Merge pull request 'Stop overriding page title and #site-title with siteName (AI)' (#4) from claude into main
Reviewed-on: #4
2026-10-27 15:43:31 +01:00
biosiasandClaude Opus 5.5 bef6538cb6 Stop overriding page title and #site-title with siteName (AI)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:05:44 +01:00
biosias 84c349b28d Merge pull request 'Claude' (#3) from claude into main
Reviewed-on: #3
2026-10-26 23:32:56 +01:00
biosiasandClaude Sonnet 5 96cefba24d Cache per-query search matches so repeat searches skip unchanged articles (AI)
Adds a searchQueries IndexedDB store (js/cache.js) keyed by the exact
search text, holding a { articlePath: { mtime, matched } } map per
query. loadSearch() loads it once per new query, trusts a cached
result whenever an article's mtime still matches instead of running
matchesQuery() again, and writes back whatever's newly checked each
scan round. Repeat searches for the same term — even after a page
reload — now only need to actually check articles that are new or
have changed since, not the whole site again. Capped at
MAX_CACHED_SEARCHES (30) distinct queries with least-recently-used
eviction so trying many different terms over time doesn't grow the
cache unbounded.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:18:03 +02:00
biosiasandClaude Sonnet 5 4e2229fed9 Log hydration failures instead of swallowing them silently (AI)
hydrateArticleNow()'s catch-all discarded network, HTTP, and parsing
errors alike with no diagnostic, using the same fallback for all of
them. A real bug in parseFrontmatter()/deriveContentFields() would
silently leave an article title-less forever with no way to tell it
apart from a transient fetch blip. Now logged via console.error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:08:17 +02:00
biosiasandClaude Sonnet 5 2b2cdb1d3c Translate the optional #site-title header link at startup (AI)
document.title was already set from the siteName string, but a
visible #site-title header link stayed hardcoded in whatever language
it was typed in — a translated site showed a translated tab title
next to an untranslated header. Set once in router.js's init() rather
than showListView(), so a page that loads straight into an article
route still gets it. Documented #site-title as optional embedding
markup and corrected README's claim that <title> isn't JS-templated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:07:45 +02:00
biosiasandClaude Sonnet 5 057f6673cb Cap concurrent per-author listing fetches during indexing (AI)
Wave 2 of the crawl fetched every author's directory listing via a
plain Promise.all, with no limit — a site with hundreds of authors
fired one simultaneous request per author on every cold-cache load.
Routed through mapWithConcurrency/MAX_CONCURRENT_REQUESTS, the same
cap hydrateArticles() already respects for content fetches.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:05:54 +02:00
biosiasandClaude Sonnet 5 cf2733368f Catch render() errors from the progress-listener callback (AI)
The onArticlesProgress listener invoked async render() without
awaiting or catching it, unlike the final render() call, which is
inside a try/catch. A mid-crawl render failure was an unhandled
rejection that left the list stuck on its loading state forever
instead of showing articlesLoadError.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:04:49 +02:00
biosiasandClaude Sonnet 5 6d424ffbc9 Stop crawl-health warning false-positiving on non-article files (AI)
The loaded/total ratio check treated a low filename-match rate as a
sign of network/CORS breakage, but indexing does no per-article fetch
at all — a low ratio just means many non-conforming .md files coexist
with real articles, which is normal and not a health problem. Removed
that heuristic and its now-unused crawlHealthArticlesFailed string;
real breakage is still fully caught by the authors-failed check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:04:10 +02:00
biosiasandClaude Sonnet 5 c3d8f0171c Fix blank line silently dropping YAML block arrays (AI)
A blank line between a block-array key and its "- item" lines matched
neither the item nor the key regex, resetting arrayKey to null and
causing the following items to be silently skipped. Blank lines are
now ignored instead of ending the array.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:02:38 +02:00
biosiasandClaude Sonnet 5 ae9bcfec3a Fix IndexedDB cache being defeated when mtime is unparseable (AI)
resolveStub() required a non-null mtime to reuse a cached hydrated
entry, so any listing without a parseable mtime (HTML-autoindex
fallback edge cases) rebuilt every stub from scratch on every crawl.
Comparing mtime directly (including null === null) trusts the cache
when there's no evidence either way, same as before this changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:02:31 +02:00
biosiasandClaude Sonnet 5 c8464398b9 Replace underscore draft convention with author/draft/ folder (AI)
Drafts now live in an author's own draft/ subdirectory instead of a
leading-underscore filename prefix. The crawler already skips
subdirectories when listing an author's files, so nothing under
draft/ is ever indexed; nginx should also deny that path outright so
drafts aren't fetchable by a guessed direct URL either.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 15:49:00 +02:00
biosiasandClaude Sonnet 5 730f16934d Remove category/year filtering, keep tag-only filtering (AI)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 15:19:59 +02:00
biosiasandClaude Sonnet 5 91a24574a8 Remove last literal mentions of the old project name (AI)
cache.js's comment explaining the DB_NAME prefix change referenced
the old "cmjs-" prefix literally as a historical note. Reworded to
describe the effect generically instead, so no trace of the former
project name remains anywhere in the repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 14:58:17 +02:00
biosiasandClaude Sonnet 5 16395fa7c5 Rename project from cmjs to cmkiss (AI)
Updates every in-repo mention: README.md title/prose/example paths,
code comments in config-default.js and router.js, and the IndexedDB
DB_NAME prefix in cache.js (cmjs-cache-v3 -> cmkiss-cache-v3).

The DB_NAME change has the same effect as a schema-version bump --
every returning visitor's browser opens a fresh, empty cache under the
new name and the old cmjs-cache-v3 database is left as orphaned
storage. Harmless (cache.js degrades to live-fetch when the cache is
empty), noted in a comment there rather than left as an unexplained
side effect.

Two things this commit does NOT cover, both outside this checkout:
- The local directory itself (still /home/user/Projects/cmjs) --
  renamed separately right after this commit.
- The git hosting-side repo name/remote URL
  (git.astronomickaexpedice.cz, Astronomicka_Expedice/cmjs.git) --
  requires server-side access this session doesn't have; needs a
  manual rename on the host plus a local `git remote set-url` update
  to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 14:56:09 +02:00
biosias 62f909ead8 Merge pull request 'Fix configuration management' (#2) from claude into main
Reviewed-on: Astronomicka_Expedice/cmjs#2
2026-09-12 16:53:42 +02:00
biosiasandClaude Sonnet 5 9e8af56069 Make config.js truly optional, falling back to config-default.js (AI)
A plain static `import ... from './config.js'` can't gracefully
degrade if that file doesn't exist -- a missing module breaks the
whole graph before any application code can catch it. So the four
consumers (discovery.js, i18n.js, templates.js, router.js) now import
BASE_URL/MARKED_URL/ARTICLES_URL from config-default.js instead, which
tries a dynamic import() of the optional config.js (catchable, unlike
a static import) and merges in whatever it exports, falling back to
its own shipped defaults for anything config.js doesn't override --
or doesn't exist at all. Top-level await makes every importing module
wait for that resolution first, same as a plain synchronous export.

config.js itself is now just an optional, empty-by-default file: only
export the specific constant(s) you want to change. Updated the
README's "Local configuration" section (and its three cross-references
in Running locally / Embedding in an existing page / Pointing
articles/ somewhere else) to drop the now-unnecessary "copy this file
before first use" step entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 16:51:18 +02:00
biosiasandClaude Sonnet 5 c4d39ea1ad Split config.js into config-default.js (tracked) + config.js (gitignored)
Lets cmjs be used as a git submodule (or updated by any other means)
without a local config override -- e.g. a custom ARTICLES_URL -- being
clobbered by the update. js/config-default.js now holds the shipped
defaults and is tracked; js/config.js is gitignored and re-exports
those defaults unless overridden, and is what every other module
actually imports from.

Since js/config.js no longer ships with the repo, added a required
one-time setup step (cp js/config-default.js js/config.js) -- new
"Local configuration" section in the README, cross-referenced from
"Running locally", "Embedding in an existing page", and "Pointing
articles/ somewhere else".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 16:47:02 +02:00
biosias 78765b95f5 Merge pull request 'Finalizing the code' (#1) from claude into main
Reviewed-on: Astronomicka_Expedice/cmjs#1
2026-09-12 16:35:28 +02:00
biosiasandClaude Sonnet 5 243319aae3 Remove unused isCacheAvailable() export (AI)
Dead code -- nothing in the codebase imported or called it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 16:30:01 +02:00
biosiasandClaude Sonnet 5 eaf202ec83 Document recent behavior changes in README (AI)
Found while reviewing README completeness against the last bug-fix
pass:
- "Crawl health" bullet didn't mention the new rootFailed case (the
  root author-listing fetch failing outright, distinct from every
  individual author's listing failing).
- "Back link" feature bullet still described the old fixed-to-page-1
  behavior instead of the fixed return-to-referring-page behavior.
- No mention that #list-view/#article-view are checked for at startup
  with a friendly error, unlike the truly-optional mount points.
- No mention that author directory names are case-sensitive and
  matched exactly against #author/... URLs and profile lookups.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 16:27:17 +02:00
biosiasandClaude Sonnet 5 24f9740d97 Fix bugs and robustness gaps found in a full code review (AI)
Real bugs:
- loadArticle()/loadAuthorView() had no stale-render guard (unlike
  loadArticleList()/loadSearch()) -- a slower, superseded request could
  overwrite a newer one's render. Both now capture and check a
  generation snapshot.
- #post/... links interpolated article.hash (raw, unencoded
  "author/slug") directly, but router.js unconditionally
  decodeURIComponent()s each segment when parsing -- would throw
  (silently swallowed) for any author/slug containing a literal "%" or
  other reserved character. Links now go through a new postHref()
  helper that encodes each segment individually.
- A startup failure replaced the entire document.body, which would
  wipe out a host site's own page content when cmjs is embedded
  alongside it. Now scoped to cmjs's own mount points when at least
  one exists.
- hydrateArticle() had no in-flight de-duplication -- two concurrent
  callers for the same not-yet-hydrated stub (e.g. a page's hydration
  racing the next page's prefetch) could each fire a duplicate fetch.
  Now tracked via a WeakMap keyed by the stub.
- Leftover "expedition blog"/"expedition year" wording missed by the
  earlier astronomy-club genericization pass (that pass's grep didn't
  cover the English word "expedition").

Robustness gaps:
- cache.js no longer permanently disables the whole IndexedDB cache
  after a single failed transaction -- only openDb() itself failing
  does that now.
- Listing mtimes are normalized to minute precision at the one point
  they enter the system, so cache comparisons stay consistent even if
  a server flips between JSON and HTML-fallback autoindex.
- The root author-listing fetch failing outright is now treated like
  "every author's listing failed" (a new rootFailed crawl stat) instead
  of surfacing a generic, less-specific error.
- router.js now checks both required mount points exist right after
  startup and fails with a clear, catchable message instead of a bare
  TypeError the first time showListView()/showArticleView() runs.
- The article view's "back" link now returns to whatever list/search/
  filter page the user actually came from (tracked via lastListHash)
  instead of always resetting to page 1 of everything.
- filterLabel() now checks filter.type === 'category' explicitly
  instead of falling through to it as the default case.
- Author-name filtering is now case-sensitive, matching how author
  paths/profile lookups are looked up everywhere else.
- Documented (in code comments and a new README note) that article/bio
  Markdown renders via marked.js with no HTML-sanitization pass --
  only trusted authors should be adding content.

Minor:
- Extracted applyArticleMetaFields() to share the date/reading-time/
  category-badge logic between renderCard() and loadArticle() instead
  of duplicating it.
- Clarified resolveMediaUrl()'s WordPress-migration special case as
  legacy compatibility, not a general-purpose feature.
- frontmatter.js no longer requires a trailing newline after the
  closing "---" -- a file that's nothing but frontmatter now parses
  instead of falling through to "no frontmatter at all".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 16:23:26 +02:00
biosiasandClaude Sonnet 5 a0f1ed67d9 Document embedding search, article list, and tag list independently (AI)
New README subsection under "Embedding in an existing page" explaining
that #list-view/#article-view (the only required pair, also where
search results render), #search-form/#search-input, #tag-filters, and
#year-filters are all independent mount points that can be placed
anywhere in a custom layout rather than stacked the way the demo does
-- includes a #year-filters example, which wasn't documented anywhere
before despite still being fully supported by renderFilterNavigation().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 16:00:20 +02:00
biosiasandClaude Sonnet 5 22f17a748b Make the articles/ folder location independently configurable (AI)
Added ARTICLES_URL to js/config.js, defaulting to the current
behavior (a sibling of js/ at the repo root) but editable to point
anywhere under the same origin (or a different one, CORS permitting)
-- articles no longer have to live alongside the rest of cmjs.

discovery.js's three hardcoded `${BASE_URL}articles/...` usages
(author listing, per-author directory, author bio fetch) now all go
through ARTICLES_URL instead.

Documented in README's nginx configuration section, including that
whatever ARTICLES_URL points at needs its own matching autoindex
location block.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 15:53:35 +02:00
biosiasandClaude Sonnet 5 e739c7cf8c Move marked.min.js into its own vendor/ folder (AI)
Third-party code is now kept separate from the app's own js/ modules.
Updated js/config.js's MARKED_URL to the new sibling-directory path
(../vendor/marked.min.js) and the README's project structure diagram
accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 15:28:35 +02:00
biosiasandClaude Sonnet 5 3c510ba92e Move the demo page into its own demo/ folder (AI)
index.html and its site-shell CSS (reset.css, header.css, layout.css)
now live under demo/, separate from the library's own css/, js/,
strings/, templates/, and articles/ at the repo root. Root css/ now
holds only css/components/ — the styles any embedding page actually
needs.

Fixed demo/index.html's own relative paths to the component CSS and
router.js (one level up now); its own site-shell CSS links needed no
change since they moved along with it. No BASE_URL/MARKED_URL changes
in js/config.js — those resolve from the script's own location via
import.meta.url, independent of wherever the HTML page that loaded it
lives.

Updated README.md throughout: project structure diagram, Translations'
index.html mentions, the Embedding section's site-level CSS example
paths, the nginx config's SPA location block (now /demo/ instead of
root), and Running locally's preview instructions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 15:24:47 +02:00
biosiasandClaude Sonnet 5 1dc5e16d14 Flatten cmjs/ into the repo root (AI)
The repo is already named cmjs, so the nested cmjs/ subfolder was
redundant. Moved css/, js/, strings/, templates/, and the gitignored
articles/ up to the repo root and removed the empty cmjs/ directory,
so the whole repo (or just css/js/strings/templates/articles) can be
dropped into an existing site and embedded as-is — no wrapper folder
required.

This also fixes a real bug: js/config.js's MARKED_URL previously
reached outside cmjs/ (../../js/marked.min.js) to find the vendored
marked.min.js, which already lived at the repo root as a sibling of
cmjs/ rather than inside it. Now that both files sit in the same js/
folder, MARKED_URL is a same-directory reference and no longer depends
on anything outside this one folder tree. BASE_URL's own formula is
unchanged (config.js is still exactly one level below whatever holds
articles/, strings/, and templates/ — previously that was cmjs/, now
it's the repo root).

Updated index.html, .gitignore (/cmjs/articles/ -> /articles/), and
every path reference throughout README.md (project structure diagram,
embedding instructions, nginx config, discovery/caching docs) to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 15:20:19 +02:00
biosiasandClaude Sonnet 5 d3c6789cfe Remove the tag label registry entirely (AI)
Tag ids are now displayed as-is everywhere a label was previously
looked up (card/article pills, the tag filter sidebar, the tag filter
heading, and search matching) — no more cmjs/blog-config/tags.json,
fetchTagRegistry(), or the tagLabels/ensureTags() machinery in
views.js that read it.

The tag filter sidebar now excludes year-YYYY ids by an explicit
regex instead of relying on the registry never listing them (that was
an accidental side effect of iterating tagLabels' own entries).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 13:40:33 +02:00
biosiasandClaude Sonnet 5 e7f8939ced Replace astronomy/expedition-club content with generic placeholders (AI)
The demo tag registry, site name, and the year-category tag convention
(expa-YYYY) were all modeled on a specific Czech astronomy club. Swapped
in generic, English placeholders instead:

- blog-config/tags.json: 12 generic topic tags (guides, news,
  tutorials, ...) replacing the Czech astronomy-themed ones.
- The year/category tag convention is now `year-YYYY` instead of
  `expa-YYYY` (discovery.js's category regex, the derived field renamed
  expa -> categoryYear, and every views.js/README usage).
- siteName and yearLabel strings (en/cs/sk) and index.html's hardcoded
  title/header text no longer say "Expedition Blog(s)" / "Expa {year}".
- README examples updated to match (kometa -> guides, expa-2026 ->
  year-2026).

Also regenerated all test content (cmjs/articles/, gitignored) with the
new generic tags and year- prefix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 13:35:23 +02:00
biosiasandClaude Sonnet 5 997768e2ae Flatten article storage: one file per article, shared author media folder (AI)
Articles no longer live in their own per-post directory — each is now a
single `<slug>~<DDMMYYYY>~<tag1>+<tag2>.md` file directly inside its
author's directory, alongside that author's bio file and a shared
`media/` folder for images. This lets multiple articles reference the
same picture without duplicating it per article.

discovery.js: buildStub/resolveStub parse the article's own file name
instead of a wrapping directory's; indexing's per-author listing now
filters to `.md` files (excluding the bio file) rather than
subdirectories, and the author's own media/ folder is simply not a
`.md` file, so it's excluded for free. The article file's path is now
also its own cache key (the `root` concept is gone).

cache.js: bumped CACHE_SCHEMA_VERSION (cache key shape changed) and
renamed the object store from the now-inaccurate "slugDirs".

Migrated all existing test content (10,200 articles) to the new flat
layout; no code changes needed to the articles' own content.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 13:25:04 +02:00
biosiasandClaude Sonnet 5 5c716a3f9b Document nginx compression for markdown/JSON transfers (AI)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 13:14:13 +02:00
biosiasandClaude Sonnet 5 e217d76695 Pause search hydration once a page's worth of matches is found (AI)
loadSearch now scans stubs in small batches and stops as soon as it
has enough matches for the requested page (plus one, to know whether
a next page exists), instead of hydrating every article up front.
Scan progress is kept per-query so paging forward resumes without
re-checking, and paging backward needs no scanning at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 13:07:37 +02:00
biosiasandClaude Sonnet 5 885178d1e7 Switch the post-directory field separator from -- to ~ (AI)
A double-hyphen separator collides with an easy authoring typo: an
accidental extra "-" in a kebab-case slug (e.g. "hello--world")
silently misparses as slug "hello" + an invalid date, dropping the
article from the index with no visible error. "~" isn't something
anyone would type by accident while writing a normal slug, and
(like "-") is in the URI "unreserved" set, so it never needs
percent-encoding either.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 12:32:13 +02:00
biosiasandClaude Sonnet 5 122593da24 Encode article metadata into the post directory name (AI)
Replaces .tag/.date/draft marker files with a single naming
convention: <slug>--<DDMMYYYY>--<tag1>+<tag2>+... (optionally
prefixed with "_" for draft). This eliminates the per-article
listing fetch entirely — every stub field is now parsed straight
out of the directory name the author-level listing already returns,
so indexing costs one request per author, full stop, regardless of
how many articles exist.

discovery.js: buildStub()/resolveStub() become synchronous (pure
string parsing, no fetch); runCrawl() drops the wave-3
crawl/mapWithConcurrency/mtime-priority-ordering entirely, since
there's no more per-item async work to stage. hydrateArticle() and
everything downstream in views.js is unchanged, since the derived
article shape is identical either way.

Removes the example admin/hello-world article (old format, git
was tracking it) and regenerates 10 authors x 20 articles in the new
format for testing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-12 00:05:48 +02:00
biosiasandClaude Sonnet 5 58acdbff98 Document that hydration already prioritizes newest articles (AI)
No functional change — hydrateArticles() already processes stubs in
their existing array order, and discoverArticles() already sorts
that array newest-first by the authoritative date field, so search
results (and page hydration) already fill in newest-first. Adds a
comment making this explicit, since it's a non-obvious "free"
property of the sort order rather than something the code visibly
does on purpose.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-11 21:02:30 +02:00
biosiasandClaude Sonnet 5 36297616a5 Paginate search results (AI)
router.js: recognises a trailing /page/N on a #search/... hash,
falling back to treating the whole remainder as the query for
hand-typed/legacy links (the app itself always encodes the query as
one segment, so this is unambiguous for real usage).

views.js: renderArticleListBody() is generalized to take an explicit
heading/emptyKey/loadingKey instead of assuming the article-list's
own copy, so loadSearch() can reuse it directly with a
{type:'search', value:query} pseudo-filter driving the same
pageHash()-based pagination links as author/category/tag views.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-11 13:51:52 +02:00
biosiasandClaude Sonnet 5 7c6e13a2fe Batch cache reads and stream search results progressively (AI)
cache.js: replaces the per-directory getCacheEntry() point lookup
with getAllCacheEntries(), a single bulk read via IDBObjectStore
.getAll(). discovery.js's runCrawl() kicks this off in parallel with
the author/post-directory requests and passes the resulting Map into
resolveStub() as a synchronous in-memory lookup, cutting hundreds of
individual IndexedDB round-trips down to one.

hydrateArticles() now takes an optional onProgress callback;
loadSearch() uses it to render matches as hydration resolves instead
of blocking on the whole result set.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-11 13:43:48 +02:00
biosiasandClaude Sonnet 5 202f88b206 Cache stubs, prefetch next page, parallelize author profile fetch (AI)
resolveStub() now caches every resolved stub (not just ones that get
hydrated), so a repeat visit skips the listing fetch for articles
nothing has ever rendered too, not only already-viewed ones. Once a
page's cards render, the next page's content hydrates in the
background so "next" is usually an instant cache hit. loadAuthorView
fetches the profile and article list in parallel instead of
sequentially.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-11 13:30:27 +02:00
biosiasandClaude Sonnet 5 81fb2a2414 Defer article content fetches to page-scoped hydration (AI)
Splits discovery into a cheap indexing phase (buildStub — directory
listing only: .tag/.date markers and a new `draft` marker file, no
markdown fetch) and a separate hydrateArticle() step that only runs
for articles actually being rendered: the current page's cards, a
visited article, or a search (which needs real text, so it hydrates
everything it's searching). Sorting, filtering, pagination, and the
sidebar counts all work from stubs alone.

`draft` (an empty marker file, same pattern as .tag/.date) replaces
the frontmatter draft:/status: check entirely, since that also needs
to be knowable without a content fetch. Bumps CACHE_SCHEMA_VERSION
since the cached article shape changed (added root/hydrated fields).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-11 13:22:17 +02:00
biosiasandClaude Sonnet 5 60cf355352 Stream the article list as it's indexed instead of blocking on the whole tree (AI)
discoverArticles() now dedupes concurrent callers into one shared
crawl and exposes onArticlesProgress() for incremental results;
loadArticleList() (and everything built on it: pagination, author/
category/tag filters, the sidebar counts) renders as soon as the
first articles resolve and keeps updating while the rest crawl in
the background, with a "still indexing…" note until done. The crawl
also visits the most-recently-touched directories first (mtime,
best-effort) so early results tend to already be close to correct;
final display order is unchanged (always the authoritative `date`).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-11 13:09:55 +02:00
biosiasandClaude Sonnet 5 e58a5e1336 Co-locate images with their articles/authors instead of a shared folder (AI)
resolveMediaUrl() now resolves frontmatter image/avatar filenames
relative to the referencing .md file's own directory instead of a
shared cmjs/pictures/ folder, which is removed along with
PICTURES_URL. fetchAuthorProfile() now returns its own file path so
avatars resolve the same way. Migrates the existing hello-world
example's preview image into its article directory.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-11 12:53:16 +02:00
biosiasandClaude Sonnet 5 c05a85b21b Remove the years sidebar from index.html (AI)
Also fixes renderFilterNavigation() to render years/tags
independently instead of bailing out entirely when #year-filters
is missing, which would have silently broken tag filtering too.
The "all articles" reset link now moves into whichever section
renders first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-11 12:44:01 +02:00
biosiasandClaude Sonnet 5 601f2902f5 Fix README gaps: author-profile files, tag registry, search accuracy (AI)
author-profile.css/html and cmjs/blog-config/tags.json existed and
were used but were undocumented. Also fixes the search feature
description (it already matched tags, not just title/description/
author) and adds a tag-filtering bullet to match the routing table.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-11 12:41:41 +02:00
biosias 84f21956fa Removed export file 2026-09-11 12:37:41 +02:00
biosiasandClaude Sonnet 5 99bb85e98a Add Czech and Slovak translation files (AI)
cmjs/strings/cs.json restores the site's original Czech UI text
(now sourced as a translation file rather than hardcoded); sk.json
is a new Slovak translation. Both cover every key in en.json.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
2026-09-11 12:35:03 +02:00
biosias 3c08326a32 Initial Commit 2026-08-23 21:54:17 +02:00
biosias 0ffba293ff Initial commit 2026-08-23 21:49:02 +02:00