21 Commits
Author SHA1 Message Date
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
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
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
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 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 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 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
biosias 3c08326a32 Initial Commit 2026-08-23 21:54:17 +02:00