Conversation
… materialized content dirs
…& stats)" This reverts commit 071e5ab.
* fix(theme): hide agent instructions and brand package footer
* fix(build): resolve font assets under Astro base path
* fix(fonts): break mono variable cycle
* chore(deps): upgrade Astro integration packages
* fix(docs): update page instruction skill references
* chore(deps): upgrade Astro integration packages;fix(build): resolve font assets under Astro base path;fix(fonts): break mono variable cycle (#53)
* fix(theme): hide agent instructions and brand package footer
* fix(build): resolve font assets under Astro base path
* fix(fonts): break mono variable cycle
* chore(deps): upgrade Astro integration packages
* fix(docs): update page instruction skill references
* fix(anime): implement snapshot.keepLastValid; keep empty fetches from clobbering valid snapshots
The config key snapshot.keepLastValid (default true) was documented but never
consumed, while scripts/anime/sync.mjs unconditionally overwrote the snapshot:
a provider returning an empty list (private account, API drift, risk control)
silently replaced a valid baseline and pages degraded per fallback.kind.
- extract commitSnapshot()/readSnapshotItemCount() into
scripts/anime/snapshot-store.mjs: empty result + keepLastValid + existing
non-empty snapshot keeps the old file and returns "kept"; anything else is
written atomically; missing/corrupt snapshots count as 0 items (nothing
worth protecting)
- anime:sync prints a warning and exits 0 when it keeps the last valid
snapshot, so pipelines are not broken by an intentional skip
- dev-time live fetch no longer writes an empty snapshot file (it would
poison the missing-file bootstrap and block future live fetches)
- document the two-layer ownership: <provider>.json is the anime:sync-managed
baseline; custom source.file (e.g. manual.json) is user input that
anime:sync never writes
- cover the decision table in tests/anime-snapshot.test.mjs
Refs #56 #57
* test(content): lock in the snapshot baseline semantics in sync and export suites
- content:sync must accept and materialize anime snapshots provided by the
content repo (baseline semantics, not a build-time artifact error); this
pins the intentional absence of src/data/anime-snapshots/** from
PROTECTED_PATHS
- split the export exemption assertions: thumbnails/covers/font subsets are
never exported because content:sync would reject them, while snapshots and
.gitkeep are never exported for a different reason (they are allowed on
the sync direction; export simply never writes them back)
Refs #57
* docs(content): unify anime snapshot ownership wording across content-separation docs
cli-workflows.md and the shirone-content-workflow skill claimed content repos
providing src/data/anime-snapshots/** would make content:sync fail, while
sync.mjs accepted the file and the README directory tree expected it: several
claims contradicted the implementation in both directions.
- cli-workflows.md: drop snapshots/.gitkeep from the "sync errors" list and
describe the baseline semantics in a dedicated item
- README.md: add the snapshot exception to the build-time artifact section
(provider.json overwritten by anime:sync, custom source.file is pure user
input, export never writes back, clean never deletes)
- shirone-content-workflow skill: sync the red-line paragraph accordingly
- scripts/content/{resolve-source,export,clean}.mjs: fix the comment
rationale and note why anime-snapshots is deliberately absent from
PROTECTED_PATHS
Closes #57
* fix(timeline): sort timeline items by date according to order config (fixes #60)
* fix(music): set initial player status to idle instead of loading before interaction (fixes #58)
* fix(display-settings): constrain panel height to dynamic viewport and enable smooth scrolling (fixes #66)
* fix(anime): preserve unknown progress and avoid rendering 0/N progress bar (fixes #71)
* fix(anime): support useWebp compression and detect magic bytes for local covers (fixes #54)
* feat(ci): add anime snapshot and cover cache with --if-stale support (fixes #61)
* fix(content): invalidate Astro content store on pruned/empty collections and uncache in deploy workflow (fixes #55)
* fix(sitemap): exclude disabled pages and redirect stubs from sitemap (fixes #59)
* feat(pages): unify page metadata, custom titles, and enable switches across all content pages (fixes #64)
* feat(i18n): support custom i18n text overrides grouped by language (fixes #65)
* feat(banner): support theme-aware banner images for light and dark modes (fixes #67)
* feat(config): generate JSON schemas for config domains to provide YAML validation and completion (fixes #70)
* docs: clarify subpath deployment configuration and base URL guide (fixes #45)
* fix(fab): hide mobile controls while scrolling
* fix(music): stop faking loading before any request; add optional viewport metadata prefetch
- 初始快照不再在「没有任何请求」时谎报 loading:status 初始为 idle,
卡片显示新占位「尚未请求」(新 I18nKey musicNotRequested,十个语言模块同步),
「正在加载」只在真实请求在飞时出现(issue #58)。
- 新增 meting.preload 选项("metadata" | "none",默认 "none"):
"metadata" = 组件进入视口即预取歌单元数据(IntersectionObserver,
仅元信息、绝不预取音频流),首屏直接显示第一首曲目;
"none" = 保持现状的按需行为(存量用户零变化)。
- Meting 请求加 8 秒超时兜底(依赖注入 fetchTimeoutMs 便于测试):
第三方 API 挂起不再把卡片钉死在「正在加载」,超时走既有
source-unavailable 错误路径。
- resolveMusicOptions 归一化 meting.preload,ResolvedMusicOptions 自包含。
测试:初始态断言改 idle;新增超时兜底、preload 归一化、视口预取
(零交互即取、且幂等不重复请求)三条用例;locale 完整性测试覆盖新 key。
* chore(schema): update music schema with preload option
* chore(deps): bump the patch-updates group across 1 directory with 13 updates (#63)
* docs(design): align DESIGN.md and M3E standard with Material Design 3
* feat(theme): implement M3 typography tokens and TopAppBar scroll elevation
* refactor(blog): align PostCard typography, state layer, and control shape with M3 standards
* refactor(sidebar): align WidgetLayout, Profile, and CategoryList with M3 standards
* refactor(post): align article header, metadata badges, dividers, and post navigation with M3 standards
* refactor(sidebar): align Tags, Categories, SiteStats, and Announcement with M3 tokens
- align Chip atom with M3 pill shape contract (--shape-corner-full)
- update Tags and Categories index navigation links to use M3 typography, 8dp spacing, and state layers
- align SiteStats row layout with 8dp spacing system (--m3e-space-2)
- refactor Announcement widget with M3 typography, secondary-container actions, 40px touch targets, and error-colored close button
* refactor(shell): align SidebarTOC, FAB controls, and SiteNavigationDrawer with M3 specs
- align TocList items and markers with M3 shape tokens and state layer styles in toc.css
- standardize FloatingControls gap, translation offsets, and spacing to 8dp system tokens
- align FloatingTOCPanel title typography, header padding, and elevation tokens
- refine SiteNavigationDrawer paddings and gaps with M3 8dp spacing tokens
- update TOC integration tests for Shirone guide content and header selectors
* refactor(content): align License card, Pagination, and PasswordGate with M3 tokens
- restyle License card with surface-container-low container, outline-variant border, and M3 typography
- align PagePagination buttons with 8dp spacing and M3 control shape (--shape-corner-m)
- ensure fab-controller resets mobile visibility after post decryption
* refactor(taxonomy): align Categories, Tags, and Archive timeline with M3 typography and layout
- align Categories index page grid rows with M3 8dp spacing and state layer
- update Tags index chip cloud with 8dp spacing tokens and count badges
- standardize ArchivePanel breadcrumb and group switch spacing
- update archive filter and grouping test expectations for 2026 content
* refactor(overlay): align Search dialog and DisplaySettings panel with M3 Dialog tokens
- tokenize SearchBar expand/collapse transitions with M3 duration and easing tokens
- align SearchPanel result items with 8dp spacing and control shape contract
- standardize DisplaySettings section typography and style cell selection states
- refine mobile search button to use standard IconButton without arbitrary scale
* refactor(showcase): align Friends, Projects, Skills, and Devices with M3 Card standards
- add state layer and 8dp spacing tokens to FriendCard
- standardize padding, media gutters, and gaps to 8dp system in ProjectCard
- align SkillCard header layout and padding with M3 tokens
- standardize DeviceCard image padding, header gap, and body spacing
* refactor(pages): align Moments, Anime, Compass, and Albums with M3 tokens
- align MomentCard padding, author header, and badge spacing to 8dp tokens
- standardize AnimeCard body padding and status pill tokens
- update CompassTile with M3 state layer and 8dp spacing
- refine AlbumCard body padding with 8dp tokens in both Astro and Svelte implementations
- allow subpixel tolerance in moments viewer layout stability test
* refactor(about): align About page layout with M3 Expressive tokens
- standardize import aliases to avoid relative path traversals
- align About card padding to responsive 8dp tokens (p-6 lg:p-8)
- use --shape-corner-l for wrapper container border radius
* test(site): verify M3 compliance across all routes and update Playwright regression suite
- update banner carousel tests to safely parse light/dark theme images and skip when under minimum count
- align motion spec archive collapse count expectations with 2026 content
- confirm all 36 light and dark a11y checks pass across all 18 routes with 0 violations
* style(imports): organize imports in Categories, Tags, License, and About
* fix(blog): preserve pure card background and replace muddy hover tint with elevation lift
- eliminate muddy color-mix(on-surface 4%) hover background on PostCard
- retain clean card background and provide smooth elevation lift with translateY(-2px)
* fix(blog): keep PostCard strictly flat on hover without elevation lift
- eliminate 3D physical lifting (translateY(-2px) and elevation-2 drop shadow)
- preserve completely flat editorial aesthetic on hover
- convey interaction purely through title color highlight and subtle chevron slide
* fix(navigation): align Menu container with M3 large rounded shape and outline
- update Menu outer container from extra-small (4px) to large (16px, --shape-corner-l)
- add outline-variant border for clean boundary against varied backgrounds
- adjust menu item radius to medium (12px, --shape-corner-m) for concentric nesting
* fix(anime): repair --if-stale freshness filter (fixes #73, #75)
* fix(music): shuffle mode starts at a random track when the playlist is fetched (#74)
* fix(ui): remove Menu outer border and fix sub-item dot background in TOC
- remove unwanted outer border line on Menu dropdown container
- ensure TOC sub-item marks remain transparent so dots do not produce solid block backgrounds
- scale active TOC dots smoothly with primary color without background collision
* fix(integration): treat dev-server pseudo resolutions as failures in fallback-resolver
Under a user's strict pnpm project, `astro dev` failed to resolve
`@swup/astro/serialise`, `@swup/astro/idle` and the `@swup/astro/client/*`
modules that `@swup/astro` injects into `astro:scripts/page.js`, while
`astro build` worked.
The fallback resolver was being invoked, but its direct-resolution probe
came back truthy: Vite 8's builtin dev resolver maps a bare specifier
whose importer is a virtual module onto a root-relative pseudo path
(`<root>/@swup/astro/serialise`) instead of returning null. The plugin
read that as "the project resolves it", stood down, and
`vite:import-analysis` threw "Failed to resolve import". Vite's dev alias
plumbing can similarly answer with a `vite:alias.noResolved` sentinel.
isGenuineResolution() now rejects both shapes — an absolute id must exist
on disk, the alias sentinel counts as failure — so the anchor fallback
runs in dev like it already did in production builds.
* fix(toc): correct H2 indentation to distinguish from H1 in table of contents (fixes #77)
* fix(integration): supply the trailing slash the image endpoint route … (#79)
* fix(integration): supply the trailing slash the image endpoint route needs
In package mode every dev-server image request 404s. `image.endpoint.route`
is normalised once, by the zod transform in Astro's
`core/config/schemas/relative.js`, which runs from `validateConfig` during
`resolveConfig`. At that point `trailingSlash` is still the default `"ignore"`,
so the route stays `/_image`. This integration sets `trailingSlash: "always"`
later, in `astro:config:setup`, and `hooks.js` only re-runs
`validateConfigRefined` afterwards — which does not include that transform —
so the route is never normalised. The URL builder then emits `/_image?…`
while the route pattern compiled from `trailingSlash: "always"` expects
`/_image/`. Build and preview are unaffected; only `astro dev` 404s.
Supply the slash directly. The route now matches the pairing the transform
would have produced, and the comment carries the table so a future change to
`trailingSlash` changes this with it. If Astro ever re-runs the transform
after `config:setup` this line becomes redundant but harmless. Tracking
withastro/astro#11568 and #10149.
The first attempt made `trailingSlash` overridable through `siteConfig` and
derived the route from it, so users could pick `"always"` / `"never"` /
`"ignore"`. That was dropped: the theme's pages and route patterns assume
`"always"` throughout, so exposing the choice invites a configuration the
theme does not actually support, and the derived route was more machinery
than one constant. Hardcoding is the smaller surface and the honest one.
Source mode is unaffected and needs no change: its `astro.config.mjs` sets
`trailingSlash: "always"` inside `defineConfig`, so the transform already sees
the final value and appends the slash itself. This integration does not run in
source mode at all.
* feat(config): share the Astro integration options between both modes
The theme is configured twice and the two declarations share nothing:
astro.config.mjs (source mode) and src/integration/index.ts (package mode).
Neither imports the other, so a change to one silently skips the other and
nothing reports it. That is how sitemap() lost its filter on the package side,
how swup's persistTags lost its :not([data-swup-optional]) selectors, and how
this file's icon().include came to carry a stray "preprocess: vitePreprocess(),"
key while omitting the two collections behind 329 of the 340 icon references
in src/.
Add src/config/integrationsConfig.ts holding the options that should never
have differed, and read them from astro.config.mjs. The package side follows
in the next commit.
Only options are shared. Wiring stays where it is, because the two modes are
not symmetric: the vite aliases resolve against different roots and dependency
trees, svelte's preprocess comes from this repo's svelte.config.js here but has
to be passed explicitly in package mode, optimizeDeps needs filtering there, and
expressive-code's themes and plugins have to go through loadConfigModule /
loadPackageModule for a user's overrides to apply. Each of those is called out
at the use site rather than left to be rediscovered.
Verified against a build of the previous config, not just against the checks:
43 pages, and the only content difference anywhere in dist is page.js gaining
the brackets around swup's ignore -- '@swup/astro' declares it as
(string | RegExp)[] | a function, and the bare string here only ever worked
because this file is untyped JS. Every other diff is a value that changes on
every build (astro-island uids, m3e-tooltip and pi-wavy-clip ids, the PBKDF2
salt on encrypted posts); the svelte scope hashes are identical, which is the
part cssHash is responsible for. The icon collections regenerate
byte-identically: astro-icon was already auto-discovering the two missing sets
in a flat node_modules, so declaring them changes nothing here and fixes
package mode, where there is no such fallback.
The image endpoint route is now set in this file too. It produces the same
string Astro's relative transform would have appended, so source mode is
unchanged -- confirmed in dev: /_image/ answers 200 with a real WebP, /_image
still 404s under trailingSlash "always". Setting it here is what lets the next
commit drop it from the package-only list.
astro check 0 errors (267 files). node --test 345 passing, 13 of them new and
pinning the values themselves -- the parity check in the shirones pipeline only
compares shape, so a shared value that is wrong would otherwise be wrong in
both modes at once.
* refactor(integration): read the shared integration options in package mode
Package mode now consumes src/config/integrationsConfig.ts instead of
carrying its own copy, so both entry points are driven by one declaration.
Three user-visible fixes come with it:
- sitemap() gains the filter. It was called bare, so pages disabled through
their config were still listed in npm users' sitemap.xml. The source config
has always filtered them. The filter is loaded through loadConfigModule
rather than imported so a user's own sitemapFilter wins; it ships inside the
package, so the packageSrc fallback always finds it and nothing new has to
be scaffolded into a user's project.
- swup's persistTags regains :not([data-swup-optional]). Without it, the
per-page stylesheets the theme marks optional -- katex, overlay-scrollbars,
the syntax-highlight packs -- stayed applied after a Swup navigation to a
page that does not want them.
- vite.build.esbuild stays out. Sharing it would have started stripping
console.log and debugger from a user's own code, so the source-mode option
is left where it is and the omission is documented at both use sites.
The literals that the published types want narrowed (swup's theme, vite's
minify/cssMinify, mdx's syntaxHighlight) are narrowed with `as const` in the
shared module rather than papered over with casts at each call site.
The image endpoint route and the trailing slash now come from the shared
constants, and the music-sidebar plugin shares its virtual id and its
bundle-pruning predicate with the source-mode twin. The two plugin objects
still differ, because the sidebar path they resolve does.
astro check 0 errors (267 files). node --test 345 passing. The pipeline run
that proves the packaged build is the next step, not this one.
* fix(config): type the shared integration options so typos are caught
Audit finding, not a new feature. Moving the option literals out of the two
call sites and into src/config/integrationsConfig.ts silently removed their
type checking: an exported object with no annotation gets its type inferred
from the literal, and spread properties do not trigger excess-property
checking, so a misspelled key sailed through astro check in both modes at
once. Verified by injecting `TYPOKEY` into swupOptions — 0 errors after the
refactor, where the same key inline before it reported ts(2353).
Annotate each export with the consuming function's own parameter type, via
`Parameters<typeof fn>[0]` rather than a hardcoded name so the annotations
track the integrations' declarations. All imports are `import type` and are
erased at compile time. Re-running the injection across swupOptions,
viteBuildShared, mdxOptions and expressiveCodeShared now reports an error for
each.
Two things fell out of adding the annotations:
- `onwarn` had hand-written parameter types narrower than rolldown's
OnwarnFunction, which made the whole object fail to check. Let the types be
inferred instead. The warning argument is a RolldownLog object, never a
string, so reading `.message` was always safe — that part was not a bug.
- `swupForwardOptions` earns its existence again. Without the annotation the
split was pointless and the two objects could be merged; with
`Partial<Options>` in place, merging them fails with ts(2353). The comment
said the split was working around a check that no longer applied. Now it
says what actually rejects the keys, and warns against removing the
annotation to make the merge possible.
Also drop three tests that asserted nothing beyond restating the source — a
deepEqual of mdxOptions, a deepEqual of prebundleSpecifiers, and an
icon-key check already implied by the exact-key-set assertion two tests above.
Ten tests remain, each guarding a behaviour or an invariant rather than a
literal.
Docs: npm-package-mode.md's "Things to keep in sync" and packaging-contract.md's
"Adding things" now cover integration options, and src/config/README.md records
that this module is the one file in that directory with no barrel export and no
user-overlay layer. sitemapFilter.ts was missing from that table too.
astro check 0 errors (267 files). node --test 342 passing. astro build
43 pages, and page.js still carries the array form of swup's ignore. biome
clean on both new files.
* fix(config): derive the vite build type from astro instead of vite
Annotating viteBuildShared with vite's own BuildOptions made the shirones
release fail: its undeclared-import scan reported `vite` as a package the
shipped source imports but nothing declares. The scan is right that `vite` is
not in the theme's dependencies — it is a peer of astro, resolved from the
user's tree — and wrong to report it at all, since an `import type` is erased
at compile time and can never produce an unresolved-import error. Both sides
needed fixing; this is the theme side.
Derive the type from `AstroUserConfig["vite"]["build"]` instead. astro is a
declared dependency, so no new dependency surface, and the resulting type is
astro's own `BuildEnvironmentOptions` — which is the more accurate target:
it is exactly what astro.config.mjs would have checked the same literal
against before the options moved here. Re-injecting a misspelled key still
reports ts(2353) against that type.
astro check 0 errors (267 files). node --test 342 passing. astro build 43
pages. biome clean.
* fix(fancybox): restyle for Fancybox v6 and tolerate inlined custom CSS (#78)
* fix(comments): keep the giscus iframe canvas transparent in light mode (fixes #80)
外壳曾把 `.giscus-frame` 的 `color-scheme` 收窄成 `normal`,覆盖掉 giscus 自带
`default.css` 的 `light dark`,等于把 iframe 声明成 light-only。浏览器偏好为暗色
而站点仍在亮色模式时,Chromium 会给这样的 iframe 强制铺一层不透明深色画布,
评论区整块变黑,与亮色卡片背景割裂。
改为声明 `color-scheme: light dark`:iframe 画布保持透明,留白透出卡片底色,
iframe 内部配色仍由 giscus `data-theme` 自绘,不改变既有外壳边界。
- `tests/giscus-shell-contract.test.mjs`:编译组件内联 Stylus,锁定外壳不得再把
`color-scheme` 收窄为 `normal` / `only light`;
- `tests/site/comments.spec.ts`:新增暗色浏览器偏好下的计算样式断言;
- `rules/pitfalls.md` §2.3 与 `docs/on-demand-loading.md` 记录该 iframe 契约。
* fix(nav): drop entries for disabled features in dual-repo mode (fixes #81)
在双仓模式下 `config/nav-bar.yaml` 是整体替换,条目不会经过默认导航结构里
`...(xxxConfig.enable ? [...] : [])` 的分支,于是功能关掉后入口仍在,
点进去被 `/404/` 拦截。
- 新增 `pruneUnavailableNavLinks()`(`src/utils/nav-utils.ts`):按站内路由
(去尾斜杠、忽略查询串与哈希)递归裁掉指向已关闭功能页面的入口,
空掉的下拉分组一并隐藏;站外链接与锚点不受影响。
- `src/config/navBarConfig.ts`:默认结构与内容仓声明式条目统一在此汇合后裁剪,
默认列表移除 enable 分支,单一机制避免两处判定漂移。
- 测试:`tests/nav-utils.test.mjs` 覆盖裁剪规则与分组隐藏,
`tests/site/top-app-bar.spec.ts` 断言顶栏与移动端抽屉的入口与开关一致且无死链。
- 文档:`rules/pitfalls.md` §3.3、`docs/content-separation/config-overlay.md`、`src/config/README.md`。
* chore(deps): bump the patch-updates group with 2 updates (#84)
Bumps the patch-updates group with 2 updates: [@iconify-json/material-symbols](https://github.com/iconify/icon-sets) and [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome).
Updates `@iconify-json/material-symbols` from 1.2.91 to 1.2.92
- [Commits](https://github.com/iconify/icon-sets/commits)
Updates `@biomejs/biome` from 2.5.12 to 2.5.13
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.5.13/packages/@biomejs/biome)
---
updated-dependencies:
- dependency-name: "@iconify-json/material-symbols"
dependency-version: 1.2.92
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: patch-updates
- dependency-name: "@biomejs/biome"
dependency-version: 2.5.13
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: patch-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(deps): bump minor-updates excluding katex
Bump dependencies from PR #85 excluding katex to avoid KaTeX version mismatch with rehype-katex@7:
- subset-font: 2.5.0 -> 2.7.0
- svelte: 5.56.8 -> 5.57.0
- @playwright/test: 1.62.1 -> 1.63.0
- vite: 8.2.2 -> 8.3.0
* feat(integration): detect in-repo mode
- paths.ts: add detectInRepoMode() (projectRoot===packageRoot, case-insensitive); in-repo defaults configDir/dataDir/contentDir to packageSrc equivalents.
- types.ts: add isInRepo to ResolvedShironesPaths.
- index.ts: log mode as 'in-repo (source)'; skip override registry + overlay plugin + registry watcher in-repo; keep invalidateConfigCache; skip pagefind hook in-repo (repo CLI pagefind reads pagefind.yml); move source-only esbuild drop/pure to top-level vite.esbuild gated to isInRepo && build.
- fonts.ts: fontCacheDir() returns src/assets/fonts/.subset/ in-repo so repo toolchain (.gitignore, fonts:check) keeps working.
* fix(integration): sync post schema (alias/permalink/prevUrl/nextUrl)
- collections.ts postSchema: add alias, permalink, prevUrl, nextUrl (matching repo's inline schema).
- src/content.config.ts: keep inline defineCollection with 4 added fields (required for astro typegen; defineCollections() function call breaks astro's schema introspection).
* refactor(config): drive source mode through the integration
- astro.config.mjs: shrink from 253 lines to 6 lines (integrations: [shirones()] only).
- package.json build: remove fonts:subset step (integration now handles it).
- .gitignore: add .shirones/ (integration cache dir from load-config esbuild bundles).
* docs: source mode runs the integration
- docs/npm-package-mode.md: rewrite 'How the two modes coexist' + architecture tree + sync notes.
- docs/packaging-contract.md: update integration option table to single entry point.
- src/config/README.md: rule 4/5 + integrationsConfig.ts table row updated.
- src/config/integrationsConfig.ts: rewrite header comment (two configs -> single entry point); update IMAGE_ENDPOINT_ROUTE note to all-modes workaround.
* docs: update sync checklist for single-entry-point architecture
- rules/project-rules.md §12: rewrite intro + item 1 to reflect astro.config.mjs is now delegation-only; wiring changes go to index.ts, option values to integrationsConfig.ts.
* fix(layout): stop the page width from shifting with the scrollbar
纯色背景(wallpaper-mode=none)下站内切页时整页会横向抖一下,Banner 模式
正常。根因是 #main-layout 为 w-full 绝对定位容器,宽度等于初始包含块宽度:
经典滚动条出现/消失时整页宽度相差一个滚动条宽度(Windows Chrome 15px)。
Banner 模式文档恒高于视口、滚动条常驻,所以看不出问题;纯色模式短页
(categories/tags/文章页)没有滚动条,而 #page-height-extend 又在切页两端
临时开关滚动条,于是每次导航都横移半个滚动条宽度。
- html 常驻预留滚动条槽位(scrollbar-gutter: stable):语义是「有内容可滚动
才显示滚动条」——Banner 模式与所有长页与今天逐像素一致,短页不显示滚动条,
布局宽度与滚动条状态彻底解耦。未用 overflow-y: scroll(滚动条常显,短页会
多出无意义的一条)。
- 抽出 src/utils/scroll-lock.ts 供代码树全屏与 Mermaid 全屏复用;是否补偿由
body border box 宽度锁定前后的实测差决定——槽位预留生效时滚动条消失但布局
宽度没变,按 innerWidth-clientWidth 判断会白补 15px 压窄顶部栏。
- 新增 tests/site/layout-stability.spec.ts 锁定契约(关闭 Playwright 默认的
--hide-scrollbars,否则滚动条宽度恒为 0、契约不可观测)。
- 记录 rules/pitfalls.md §10.1 与 docs/performance-guidelines.md §2.5。
* fix(integration): strip trailing slash so in-repo detection matches
Astro passes `config.root` as a `file:` URL with a trailing slash, so
`fileURLToPath` yields `/repo/` while `findPackageRoot()` returns
`/repo`. `detectInRepoMode()` compared the two raw strings, so they
never matched and every repository checkout was misdetected as package
mode:
- `collectSiteText()` scanned a non-existent `shirones/content`, so
font subsets missed every glyph unique to the site's own content
(1513 vs 1891 unique characters measured on this repo);
- subsets were written to `.shirones/fonts/` instead of the repo's
`src/assets/fonts/.subset/`;
- the override registry and overlay plugin ran against the theme's own
sources (149 spurious overrides);
- Pagefind was indexed twice — by the integration's `astro:build:done`
hook and again by the repo's CLI build step.
Normalise trailing separators before comparing.
* fix(config): switch the demo profile link from Twitter to X
The bundled demo profile still used the legacy `fa6-brands:twitter`
bird icon and pointed at `https://twitter.com`. Rebrand to
`fa6-brands:x-twitter`, the "X" display name and `https://x.com`.
Refs #89 (icon part; the games showcase is tracked separately)
* feat(games): add a games showcase page with a landscape cover card
Adds a /games/ page following the config/data/component split of the
devices page, with an original card design: a 16:9 landscape cover
banner with the title and developer printed over a scrim, a status
pill and featured badge on the banner, and a compact meta row (rating,
playtime, platform, year), genre chips and a store link below.
- molecules/GameCard.svelte + organisms/GamesSection.svelte: search,
category filter chips with a loading transition, landscape grid
- config: src/config/gamesConfig.ts (enable / categories / disabledIds),
wired into the nav bar, sitemap filter, sidebar page list and the
nav-utils page key
- data: src/data/games.ts — NTE: Neverness to Everness (featured) and
Minecraft. Covers live in src/assets/games/ and go through the Astro
image pipeline via resolveAsset, so the build emits webp/avif variants
- i18n: 14 keys across all 10 locales
- a11y: /games/ added to the site a11y scan (light + dark)
Verified: astro check 0 errors, build emits /games/index.html, both
covers resolve to /_astro assets and load in the browser, no horizontal
overflow, no page errors, a11y scan passes in light and dark.
Refs #89
* fix(games): raise the status pill contrast to meet WCAG AA
The status pill mixed its background toward its own text colour
(color-mix of the status colour over surface-container-highest) and
printed the status colour on top. Measured 3.29:1 in light mode —
below the 4.5:1 its label-small text needs — and axe flagged a
color-contrast violation on /games/.
Use the M3 tonal pairs the theme already guarantees instead: solid
primary / tertiary / secondary / surface-container-highest backgrounds
with their matching on- colours, injected per status through inline
custom properties. The pill keeps its semantic colour coding and now
reads over any cover art.
* fix(content): register the games config domain and sync inventories (#90)
* fix(content): register the games config domain for content separation
The games showcase page merged without a CONFIG_DOMAINS entry, so a
content repository providing config/games.yaml failed content:sync and
content:validate with "has no matching config domain".
- Add the games domain (GamesConfig, @/types/gamesConfig) next to devices.
- Regenerate .vscode/schemas: new games.schema.json plus context-menu,
fab, and sidebar schemas whose SidebarPage enum now admits "games".
- Let the generator re-add the games.yaml/.yml mapping to settings.json.
* test(games): cover the /games page key and page behavior
- Extend nav-utils resolvePageKey cases with the /games route for both
root and subpath deployments.
- Add tests/site/games.spec.ts mirroring devices.spec.ts: card metadata,
featured badge, nav highlight with sidebar page filtering, category
chips with the loading transition, search with empty state, URL state
restore, and Swup entry from the persistent navbar.
* docs(project): sync games and missing domains into inventories
- config-overlay.md: complete the YAML domain table with games plus the
eight domains that had drifted out of it (about, albums, compass,
context-menu, friends, i18n, moments, permalink); bump the nav preset
table to 16 and add Games.
- content-separation README: list games.ts in the content-repo data tree.
- sidebar-system.md: add the missing devices, games, rss, and atom rows
so the SidebarPage table matches src/types/sidebarConfig.ts.
- AGENTS.md: refresh the SidebarPage identifier list to all 19 values.
- README.md: mention devices and games in the feature lists.
* docs(skills): add games to config, data, and sidebar skill inventories
- shirone-config: list games.ts among data entities and include the
games page in the page-config table row.
- shirone-data: extend the description with games and reference
tests/site/games.spec.ts in the verification examples.
- README index: add games to the shirone-data scope.
- shirone-sidebar: refresh the SidebarPage identifier examples to the
full current set.
* fix(moments): wrap long links inside moment cards on narrow screens
A moment whose body contains a long unbreakable URL widened its grid
track far past the card column: .moment-section__list used `1fr`, so
the card's automatic minimum size (min-width: auto) followed the link's
min-content width, while `.custom-md a` only set
`overflow-wrap: break-word`, which does not feed soft wrap
opportunities into intrinsic sizing. At a 400px viewport the card
measured 779px inside a 315px column and the excess was clipped.
- organisms/MomentSection.svelte: the list track becomes minmax(0, 1fr)
- molecules/MomentCard.svelte: card min-width: 0, body
overflow-wrap: anywhere
- styles/markdown.css: .custom-md a switches to anywhere, so long links
wrap inside any constrained .custom-md container (posts included).
Line breaking is identical; only min-content sizing changes.
- tests/site/moments.spec.ts: 400px regression — the link wraps onto
several lines, the card stays inside its track and the section, and
the document gains no horizontal scroll
* feat(series): add series collection, post fields and category fallback
- posts schema: optional series slug + seriesOrder; mirrored in package-mode
collections so both modes share one content contract
- new series entity collection (title / status ongoing|completed /
defaultCategory); body is the optional series overview
- series-utils: catalog loader, reading-order rule (explicit seriesOrder
wins, date fallback), per-post contexts (index/total, in-series prev/next)
- category fallback chain applied at the data layer (single choke point):
explicit category -> series defaultCategory -> uncategorized; consumed by
category index, archive grouping, post meta and cards alike
- i18n keys for series UI across all ten locales
* feat(series): series index/detail pages, in-post series card and demo content
- /series/ index: rows with status badge (ongoing/completed), latest update
date and post count, sorted by latest activity
- /series/[slug]/: optional overview body + reading-ordered post list with
part numbers; missing entity redirects to the 404 stub
- SeriesCard molecule on article pages: series link, part X of Y, status
badge and in-series prev/next chips (global prev/next untouched)
- demo: two series entities and five annotated demo posts, including one
post relying on the defaultCategory fallback
* feat(series): config domain, nav preset, sidebar widget, stats and card position
- seriesConfig domain (enable / cardPosition top|bottom) registered in the
config overlay table; series pages 404 and nav/sidebar/sitemap entries
disappear when disabled (zero burden)
- LinkPresets.Series for content-repo nav-bar.yaml; /series pruned when
disabled via unavailableFeatureRoutes
- sidebar Series widget (rows + counts + view-all footer), hidden entirely
when the site has no series; wired into SideBar registry and default list
- SiteStats gains a series row (icon + count)
- article series card position follows seriesConfig.cardPosition
(default bottom: after related reading; top: below post metadata)
- catalog loader moved to content-utils so series-utils stays pure and
unit-testable under node --test
* test(series): unit tests for series ordering, category fallback and contexts
* feat(series): sidebar order between categories and tags, count badge parity
- 侧栏默认顺序:分类 → 系列 → 标签
- 系列 widget 复用数据驱动的 CategoryList 原子,计数与分类一致地
用 secondary-container 方框徽标呈现
* feat(series): full-width overview cards on the series index
- 单栏大卡片,每系列一行:名称 + 状态徽标 + 篇数/最近更新/默认分类 +
总览摘要(excerptFromMarkdown:代码块/图片/标记清洗,链接留锚文本,
按词边界截断)
- 系列作为文章之上的概念,占地不再小于文章卡片
* feat(series): title/description overrides for the series index
- title 默认 $t:series,可字面量覆盖(resolveI18nText,与其他页面域一致)
- description 默认空 = 动态汇总「x 个系列 · y 篇文章」;配置 $t: 词条或
字面量后整体替换(SEO meta 用覆盖值,页内 subtitle 仍显示计数)
* feat(series): tooling and docs for the new domain
- .vscode/schemas/series.schema.json 生成并纳管;SidebarPage 新增 series 后
fab/context-menu/sidebar schema 与 settings 映射随生成器同步更新
- config-overlay.md:域映射表补 series.yaml;导航预设清单 16→17(含 Series 行)
- eject 生成的内容仓 README 计数含系列数;shirones init 摘要计数与
布局说明补 series
* style(series): biome organize-imports and formatting for series files
- 我方新增/改动文件全部通过 biome ci(cli.mjs/eject.mjs 的两处报错为
上游既有 lint 债,上游 CI 对 biome 步骤设 continue-on-error,不随 PR 扩散)
* feat(series): warn once per build for posts referencing unknown series
- findUnknownSeriesSlugs(纯函数):收集未知引用,每个 slug 取首篇定位
- 数据层单一入口在构建期 console.warn 一次(dev/build 日志可见);
行为保持宽容——系列卡与默认分类回退照旧跳过,不阻断构建
- 单测 +2(去重取首篇 / 全合法返回空),全量 361/361
* fix(series): normalise series slugs at the schema and every read site
- trim `series` in both content schemas (theme + package mode) so a padded
value can no longer render a card while losing the category fallback and
the series counts
- add `normaliseSeriesSlug()` as the single normalisation point and use it
in the data layer and on both series pages
- document the flat `content/series/` layout constraint and why the
in-place category write in content-utils is intentional and idempotent
* fix(series): gate every series surface on seriesConfig.enable
- the sidebar widget now short-circuits before querying the collections when
the feature is disabled, so it no longer links to /404 redirect stubs
- SiteStats drops its series row (and skips the catalog query) when disabled
- the in-post card is hidden for encrypted posts whose metadata is suppressed
- refresh the sidebarConfig header comment for the new widget type
* fix(series): render the series card on the permalink route too
posts/[...slug].astro and [...permalink].astro now build the same series
contexts and both honour seriesConfig.enable / cardPosition, so permalink
users keep the in-series navigation and the zero-burden behaviour.
* fix(a11y): name series card links by their target post
The chip label became the whole accessible name, which dropped the visible
post title; include it (WCAG 2.5.3) and add /series/ plus the detail page to
the a11y route matrix.
* test(series): cover series slug normalisation
Assert trimming/empty handling and that a padded reference still resolves
against the catalog.
* docs(project): document the series feature across config, sidebar and content docs
- src/config/README.md: seriesConfig domain row
- docs/sidebar-system.md: series widget + SidebarPage identifier
- docs/sidebar-widgets.md: widget contract table + new Series section
- docs/atomic-structure.md / docs/m3e-standard.md / docs/common-components.md:
register the Series and SeriesCard molecules
- docs/content-separation/README.md: content/series/ in the content-repo tree
* docs(skills): sync the series feature into the writing/sidebar/config skills
Cover the new frontmatter fields, category fallback, ordering and routes
(shirone-writing), the series widget and SidebarPage value (shirone-sidebar),
the config domain and nav preset (shirone-config), the content-repo mount and
overlay (shirone-content-*), and the prune-with-the-page rule for a feature's
own nav/sidebar entries (shirone-feature).
* style(sidebar): sort the Series import with its molecule peers
Keeps biome organize-imports clean for the file touched by the series feature.
* fix(series): rebuild the in-post series block as a borderless section
The block was a filled card nested inside the article card (DESIGN.md:
don't create nested cards) whose prev/next chips were invisible because
--btn-regular-bg resolves to the same surface-container-low as the card.
It now speaks the language of the section right above it (ArticleDiscovery):
no container or border, a lane heading (icon + h2 link + hairline + status
pill), a part/meaning meta line, and full-row state-layer links that mirror
the global post-nav direction language. Status uses M3 tonal pairs instead
of an ad-hoc color-mix, and the layout is token-based (space/shape/type).
* fix(series): make the index pill and detail list consistent with the block
- the index status pill reuses the same tonal pairs (ongoing = primary
container, completed = neutral container)
- the detail list drops its two-column grid: at a 1280px viewport the card
is only ~640px wide, so each row got ~280px and titles were ellipsised
while the dotted leader collapsed to its minimum
* test(series): lock the series DOM contract and design language
Covers the in-post block semantics/links/part labels, single-neighbour
cases, detail-page ordering, index statuses, and the design contract that
the block stays a borderless section (no background, no top border).
* docs(project): note the series block's section language
* docs: document series and games across READMEs, index, and workflows (#94)
- Add series and games to features and dedicated pages lists in all locale READMEs (en, zh-CN, zh-TW, ja)
- Note series entities in INDEX.md content layer documentation
- Update content:status inventory description in cli-workflows.md
* fix(license): drop the border from the post license card
The molecule mixed a filled surface (surface-container-low) with an
outline-variant 1px border, matching neither the filled nor the outlined
M3 card variant. Removing the outline leaves a consistent filled card and
matches the borderless ArticleShare block rendered next to it.
Validated with astro check (0 errors), astro build, and a computed-style
check on the built post page: 0px border on all sides, no box-shadow.
* feat: source mode integration (#97)
* feat(collections): remove defineCollections export, inline schemas only
- collections.ts: remove defineCollections() function; export postSchema, momentSchema, specSchema, createCollection helper
- index.ts: update comment about collections export
- cli.mjs: update init output message
- templates now generate inline content.config.ts with schemas
BREAKING CHANGE: package mode users must now use inline schemas in src/content.config.ts (see shirones/collections for postSchema, momentSchema, specSchema). The defineCollections() one-liner is removed.
* fix(integration): three review nits from the source-mode audit
- index.ts: the `vitePreprocess` comment still claimed source mode picks it up
from the repo's `svelte.config.js`. It does not any more — every mode goes
through the integration now, so the integration supplies it and
`svelte.config.js` is read only by editor tooling. `integrationsConfig.ts`
already said this; this one was missed.
- index.ts: the config-cache watcher matched `file.startsWith(paths.configDir)`
with no separator, while the override-directory check two lines below uses
`${dir}/`. A sibling such as `src/configFoo` would also match. Harmless in
effect (a spurious cache invalidation) but inconsistent; both now require the
separator. `configDir` comes from `join`/`resolve`, neither of which leaves a
trailing slash, so the concatenation cannot produce `//`.
- content.config.ts: restore the trailing newline dropped in fa45733.
astro check 0 errors (271 files). node --test 346 passing. Dev server boots in
in-repo mode with a clean log: `/` 200, a hashed `/_image/` request 200 at
222522 bytes, and touching `src/config/siteConfig.ts` — the path the changed
watcher guards — still serves 200.
* feat(collections): declare the collection list in a manifest
The shirones pipeline generated a package-mode user's `content.config.ts` from
a collection list hard-coded in `prepare-templates.mjs`, so every new
collection needed a matching edit in the pipeline repository — and forgetting
it produced a dangling `glob({ base })`, which only warns and then builds the
site with that collection empty.
Move the list here, where the collections are defined:
`src/integration/collections.manifest.json` carries each collection's key, glob
pattern and schema export name. The pipeline reads it, so adding a collection
upstream now needs no change over there — the directory already ships through
its recursive copy of `src/content/`.
Nothing in the theme imports the manifest at runtime, so
`tests/collections-manifest.test.mjs` is what keeps it honest: it fails if a
listed schema is not exported from `collections.ts`, if the manifest and
`src/content.config.ts` disagree on the set of collections or on a pattern, or
if a listed collection has no `src/content/<key>/` directory. Verified by
deleting the `series` entry — the test names the exact disagreement.
JSON rather than an exported constant so the pipeline can read it without
executing theme code, and without a build step (templates run before the
package build).
node --test 367 passing.
* fix(collections): drop duplicate comment opener and unused re-export
The merge that absorbed upstream's `seriesSchema` matched the conflict
region starting after the pre-existing `/**`, leaving a second opener in
front of the series doc comment. It was invisible to `astro check` — a
stray `/**` inside a block comment is just comment text — so only a
manual read caught it.
Also removes `export { defineCollection, glob, z }`. Nothing consumes
it: the generated template imports only the schemas from
`<package>/collections` and takes `defineCollection` from `astro:content`
and `glob` from `astro/loaders`. Re-exporting `z` from `astro/zod`
through a package additionally risks handing consumers a second zod
instance, which breaks `instanceof` schema checks.
* docs(integration): document override lookup and prebundle scope
`collectMetingText` explained its purpose and degradation but not the
`registryRef` parameter, which is easy to mistake for a package-mode-only
code path. Source mode needs no special case: `paths.configDir` already
resolves to the repository's own `src/config`, so `loadConfigModule`'s
package-source fallback points at the same directory.
`prebundleCandidates` explained why plugin mode returns nothing but not
why every other mode returns the full list.
* chore(release): bump the version to 1.0.3
* fix(integration)!: collapse the mode flags into a single isThemeRepo
`isPluginMode` (does `import.meta.url` contain `node_modules`?) and
`isInRepo` (is the project root the package root?) were independent, and
the pair had a hole. A linked install — `pnpm link`, a pnpm workspace,
`npm link` — reaches the theme through a symlink, and Node and Vite both
report the realpath, so the URL carries no `node_modules` segment and
neither flag came out true.
That third state was reachable by the most ordinary theme-development
setup, and it mixed the two modes: `paths.configDir` took the package
default (`<site>/shirones/config`, so a scaffold is required) while the
three gates keyed on `isPluginMode` took the source behaviour. The result
was a silently empty site — the theme's pages live in the package's
`src/pages/`, which Astro never scans, so only `injectRoute` can supply
them — with no `npx shirones init` hint to explain it, plus a full
`optimizeDeps.include` list Vite cannot resolve from the project root and
so warns about entry by entry.
Every branch only ever needs one question: is the theme building itself?
Ten call sites asked three different things and picked between two flags;
they now all read `isThemeRepo`, which makes the third state
unrepresentable. Six sites are unchanged in meaning, three are fixed
(route injection, the init hint, the pre-bundle list), and the compiler
now rejects the old pattern outright since the field is gone.
`prebundleCandidates` is exported so the linked case has a regression
test; `tests/paths.test.mjs` covers all three layouts. Verified by
restoring the old gate exactly — only the linked-install assertion fails.
BREAKING CHANGE: `ResolvedShironesPaths.isPluginMode` and `.isInRepo` are
replaced by `.isThemeRepo`.
* fix(paths): point the in-repo dataDir at src/data
The in-repo branch read the package-mode shape, `src/config/data`, which
does not exist in the repository — data modules live in `src/data`,
beside `src/config` rather than under it. The overlay target for `data`
already used `packageSrc/data`, so this was the odd one out.
The consequence was silent. `collectSiteText` walks `paths.dataDir` when
`subsetting.includeConfig` is on, found nothing, and moved on, so the
theme's own build subset its fonts without any of the text in
`src/data/*.ts` — friends, projects, anime, timeline. Measured on this
repository: 1792 characters collected instead of 1818, dropping 26 CJK
glyphs that appear only in those files (ひケ亦况夜弱春材渐演瓷眩砖笛耀耦聚观话
跨迁锚际雷頃). They would render as fallback glyphs on exactly the pages
that list them, and only in the repository build, because package mode
took the other branch.
Found by the new `resolvePaths` tests, which needed an in-repo fixture
and so asserted `dataDir` for the first time.
* feat(integration): warn on orphan config files, plus review hardening
Follow-ups from an architecture review of `src/integration/`.
**Orphan config files.** `buildOverrideRegistry` walks from the package
towards the user's project, so a file the package no longer ships is
never registered — and never mentioned. When the theme renames a config
module, the integration asks for the new name, finds neither copy, and
quietly uses the packaged default while the user's edits sit unread in
their project. `findOrphanUserFiles` reports those at startup. Only
`configDir` and `dataDir` are scanned: they belong entirely to the theme,
so an unknown file there is inert by definition. `src/components` and
`src/layouts` are deliberately left alone — they are shared with the
user's own components, so every legitimate file of theirs would be
reported as a false positive.
The scan also has to skip `dataDir` while walking `configDir`: data lives
*inside* the config directory but maps to a different package directory,
so treating it as config would flag every data module.
**Barrel exclusion.** The pattern that keeps `index.ts` barrels owned by
the package was anchored (`/^index\./`), so a nested `atoms/index.ts`
would have been overridable despite the comment saying otherwise. No
nested barrel exists today; matched at any depth now so adding one cannot
quietly break the named-export contract.
**Config bundling.** esbuild failures surfaced as a bare "Build failed
with 1 error", unlike the carefully-worded not-found error beside it; now
named with the module and the original error kept as `cause`. Superseded
bundles are pruned — each distinct hash wrote a new file into
`.shirones/loaded/` and nothing ever removed the old ones, so a dev
session accumulated one per edit. Removed `loadConfigValue`, which
nothing called.
**Docs.** `fonts.subset` was documented as falling back to
`fontConfig.subsetting.enable`; it actually defaults to
`command === "build"` and is *combined* with that flag, so `enable: true`
alone still leaves dev unsubsetted.
`command` is now derived from the `astro:config:setup` signature instead
of a hand-written `"dev" | "build"` union — Astro passes `sync` and
`preview` too, which the narrow union rejected.
* test(integration): cover the SSR node shims
`ssr-node-shims.ts` was the one brittle workaround in the integration with
no test beside it, while `fallback-resolver.ts` — equally coupled to Vite
internals — has had eight. Both are regex-driven and both break silently
across a Vite major; Astro 7 shipping Vite 8 already quietly voided a
`build.esbuild` key the theme used to set.
Thirteen cases over `transform` and `renderChunk`: which globals get
injected, that an existing declaration is reused rather than duplicated,
that `foo.__dirname` is not mistaken for the global, that a `?query` on
the module id does not leak into the injected path, and that neither hook
fires for a client build. Verified by short-circuiting each hook in turn
— three failures and four respectively.
This is unit coverage, not an end-to-end assertion on the built output.
The end-to-end path is already covered: `validate.mjs` builds the
scaffolded package-mode site, and a shim that stopped applying would fail
that build with `ReferenceError: __dirname is not defined` during
prerender. A build-artefact assertion was considered and dropped — the
site is fully static, so the SSR bundle that would carry the markers is
discarded and there is nothing on disk to grep.
* docs(readme): link the shirones wiki and recommend the npm package
Applies to all four language READMEs: README.md, README.zh-CN.md,
README.zh-TW.md and README.ja.md.
- Start Here recommends the `shirones` npm package ahead of Shirone-Content,
for anyone who would rather not track the theme source at all.
- Quick Start points at the "Use the npm package" section for the same reason.
- The npm-package section and the documentation list both link the wiki.
The wiki link goes to https://github.com/yCENzh/shirones/wiki. A custom domain
(shirones.wiki.14131413.xyz) was tried first and dropped: it answers port 80
with a 307 to the GitHub wiki, but has no TLS certificate for that SNI, so
https:// fails the handshake with alert 40 and no peer certificate. The same
Cloudflare edge IP serves other hostnames fine, so it is a missing certificate
on that hostname rather than a network problem.
Anchors are per language because GitHub's slugger keeps Unicode letters:
#use-the-npm-package, #使用-npm-包, #使用-npm-套件, #npm-パッケージを使う.
Each was checked against its own file's heading slugs.
Squashes the two commits that introduced and then corrected these links.
* fix(search): clear results when the query is emptied
* fix(series): render the bottom series block right after the article body
With `seriesConfig.cardPosition: "bottom"` (the default) the in-post series
block was rendered after the license card, the share block and
`ArticleDiscovery`, i.e. as the last element of the post card.
The `top` variant keeps the block above the article body, so `bottom` should
mirror it: directly after the body and before the closing blocks (license,
share, Continue reading). Series navigation answers "what comes next in this
series", so it belongs with the content it continues rather than below the
sign-off and distribution furniture; it also keeps the navigation specificity
order (in-series -> global post nav -> comments).
Both post routes carry the same placement, so both are updated. No markup,
style or configuration change inside `SeriesCard`.
`tests/site/series.spec.ts` now locks the reading order: the card follows
`.markdown-content` and precedes every closing block that is present (verified
to fail before this change).
* docs(config): spell out the cardPosition placement contract
The JSDoc (and the schema generated from it) still said "bottom = 相关阅读之后",
which the new placement makes wrong: bottom is now right after the article
body, before the license/share/discovery blocks. Regenerated
.vscode/schemas/series.schema.json and made the meaning explicit in the
config README row.
* docs(series): correct the reading-order note on the bottom series block
The note claimed the specificity ladder was series → global post-nav →
comments; CommentSection actually precedes LastUpdatedNotice and the global
post-nav. State the real page order instead.
* test(series): separate presence from ordering in the placement contract
Absent closers used to be reported as "name:absent" inside the ordering
assertion, so a disabled license/share block looked like a placement
regression. Presence is now asserted first and names what is missing; the
ordering assertion only compares nodes that exist. Verified the test still
fails on the pre-fix tree (closersBeforeCard = license/share/discovery).
* test(a11y): cover a series member post in the scan matrix
None of the scanned pages carried a series block, so the in-post block was
outside the axe gate even though this change moves it. /posts/markdown-extended/
scans clean in both modes.
* feat(scripts): fill publishedAt for new posts in the site timezone (#103)
`pnpm new-post` now writes `publishedAt` next to `published`, so several posts
created on the same day sort correctly without hand-editing. Original change
by Lkhsss (@Lkhsss); the review follow-ups below are folded into this squash.
- both fields are derived from one zoned "now" in `siteConfig.timeZone`
(SHIRONE_TZ > content overlay `src/user/user-config.ts` > `siteConfig.ts` >
machine zone). The previous machine-local date + hardcoded +08:00 pair wrote
posts that fail `validatePublicationMetadata()` whenever the machine and the
site are on different dates - e.g. a UTC machine after 16:00, or any site
configured with a non-+08:00 timeZone.
- invalid time zone exits with a clear error before writing anything
- biome formatting (tabs, `node:` imports)
- `tests/new-post.test.mjs` locks the contract across machine timezones
(fails 6/6 against the original implementation)
- `shirone-writing` skill documents the new scaffold output
Co-authored-by: Lkhsss <89461157+Lkhsss@users.noreply.github.com>
* build(deps): bump astro 7.3.2 -> 7.3.5
Patch-level bump to the current 7.x release. 7.3.3 changed the propagated
assets module to carry style ids alongside content; nothing in the theme reads
that shape, and 7.3.4/7.3.5 are further patches.
Verified: node --test 408/408, astro check 0 errors / 0 hints (278 files).
* fix(cli): select pinned pnpm with version-aware fallback
Use the installed pnpm directly when its version matches packageManager. On mismatch, use Corepack; only fall back to npx when Corepack is unavailable. Keep the fallback command dynamic from the packageManager value and print all three user commands without hardcoding the version.\n\nAlso include the validated patch-level dependency refresh.
* fix(cli): upgrade the project pnpm pin on update
During an explicit init --update, synchronize a previous Shirone packageManager pin to the current release. Keep ordinary init non-destructive so a user-selected package manager is not overwritten.
* fix(cli): fail closed on install errors and improve privacy defaults
Fallback to npx when Corepack exists but fails, and do not claim init completed after a failed dependency install. Detect config value drift and avoid treating any user Astro file with a slot as the starter. Use local music and disable remote font text by default; document the static encryption boundary.
* fix(build): secure transitive serializer and remove fancybox preload 404
Pin serialize-javascript to the patched release, keep Fancybox custom CSS in the eager stylesheet graph, and filter the harmless Astro MDX directive warning with an upstream removal condition.
* fix(defaults): keep friend avatars local
* revert(defaults): retain remote media sources
Keep Meting text collection enabled and preserve remote friend avatar URLs as requested.
* chore: untrack content-owned paths under public/
public/ 是内容仓的挂载目录:_redirects 被同名物化文件覆盖;favicon 作为
内容仓拥有的顶层段,sync 会裁剪主题自带的 dark/light 图标、只留内容仓的
站点图标。停止跟踪这些路径并写进 .gitignore,避免工作区永久显示脏状态。
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: yCENzh <166907745+yCENzh@users.noreply.github.com>
Co-authored-by: matsuzaka-yuki <matsuzaka-yuki@users.noreply.github.com>
Co-authored-by: Elysia <dr.zqr@outlook.com>
Co-authored-by: ATMxsp01 <ATMxsp01@gmail.com>
Co-authored-by: Dawn(黎明) <107181010+Dawn6666666@users.noreply.github.com>
Co-authored-by: KardeniaPoyu <Apoyu>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: yuxino <nbsawz@gmail.com>
Co-authored-by: ATMxsp01 <atmxsp01@users.noreply.github.com>
Co-authored-by: Lkhsss <89461157+Lkhsss@users.noreply.github.com>
The 1.0.3 upgrade landed on main as a squash commit (b533e01), so upstream/main was no longer an ancestor and the next upgrade would recompute from the old fork point b79d301 and replay all 128 commits' conflicts. This merge records the ancestry with an unchanged tree (-s ours): git diff b533e01 HEAD is empty.
The reusable content gate ran `pnpm content:validate` (a zero-write check that
type-checks the generated `src/user/user-config.ts`) before `pnpm content:sync`.
In a content-separated repo the theme clone has no `src/data/` (it is a
materialized dir, .gitignore:78), while `src/types/animeConfig.ts` imports
`../data/anime.ts`, so the type check failed on every fresh clone:
src/types/animeConfig.ts(1,32): error TS2307: Cannot find module '../data/anime.ts'
Any caller that gates work on this reusable workflow therefore went red:
content PR checks, and the content repo's `dispatch` job (`needs: validate`),
which silently stopped dispatching the Cloudflare deploy hook.
Fix follows the documented order in docs/content-separation/dual-repo-ci.md
("run pnpm content:sync to materialize posts and config, then ... build"):
add a Materialize content step before the first gate. Upstream does not hit
this because its theme repo ships demo `src/data/*.ts` as committed files.
Reproduced and fixed locally under fresh-clone conditions (src/data moved
aside + validation stamp removed): content:validate -> TS2307; content:sync ->
pass; content:validate -> pass.
makursi
force-pushed
the
fix/content-validate-materialize-first
branch
from
September 27, 2026 10:30
131c054 to
497a14e
Compare
makursi
force-pushed
the
fix/content-validate-materialize-first
branch
from
September 27, 2026 10:35
497a14e to
131c054
Compare
Author
|
作者对不起,给你添麻烦了 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
内容仓 PR #2 按官方模板还原了
validate → dispatch链之后,部署链断了:任何全新 theme 克隆里validate必挂,于是内容仓的dispatch(needs: validate)被 skip,Deploy Hook 永不被调用。根因
本 reusable workflow 的步骤顺序是
pnpm content:validate(零写盘预检,内含对生成物src/user/user-config.ts的 tsc 类型检查)在前,pnpm content:sync在后。而:src/data/(本仓.gitignore:78,来自内容分离那次002cb67),全新克隆里没有src/data/*.ts;src/types/animeConfig.ts→import '../data/anime.ts';typeCheckModule只在「生成物缺失/变化」时跑(node_modules/.cache/shirone/user-config.ok的 stamp 决定),CI 全新克隆必然触发。上游不会暴露:上游主题仓自带
src/data/*.tsdemo 数据(是提交物)——git ls-tree upstream/main -- src/data/可以看到。也就是说这是「官方闸门 × 内容分离」的边界缺陷,不是本仓改坏的(本文件与upstream/main逐字相同)。修法(按官方文档)
docs/content-separation/dual-repo-ci.md的 deploy 流程明确「流水线先执行pnpm content:sync将文章与配置同步到位,再进行…打包」,即先物化再校验。本 PR 只调顺序:Materialize content步骤(pnpm content:sync),排在两个校验之前;pnpm content:sync删掉(此时已物化)。闸门强度不变:配置类型错误现在会在
content:sync里先红;目录结构 / keep 冲突 / 配置域检查照旧。验证
在全新克隆条件下本地复现并回归(把
src/data移开 + 删掉node_modules/.cache/shirone/user-config.ok):pnpm content:validate(改前顺序)TS2307: Cannot find module '../data/anime.ts'pnpm content:syncpnpm content:validate(改后顺序)YAML 解析校验通过;改动只碰
.github/workflows/content-validate.yml(+13/-1),不涉及主题源码。影响
@main是内容仓两个调用方(PR 校验validate-content.yml、派发前的trigger-build.yml)的 ref,合并本 PR 即同时修好这两条链。MIGRATION_PITFALLS.md/ ADR 里记录这笔补丁,便于下次合上游时保留。验证步骤(合并后)
validate / Schema and structure;