Skip to content

docs (adr): select SvelteKit on Cloudflare#19

Merged
Rindrics merged 22 commits intomainfrom
issue-3-web-ui
Jan 6, 2026
Merged

docs (adr): select SvelteKit on Cloudflare#19
Rindrics merged 22 commits intomainfrom
issue-3-web-ui

Conversation

@Rindrics
Copy link
Copy Markdown
Owner

@Rindrics Rindrics commented Jan 6, 2026

User description


PR Type

Enhancement, Documentation


Description

  • Build complete SvelteKit web UI for bibliography management

  • Implement text and tag-based search with autocomplete suggestions

  • Add CI/CD pipeline for web application with path-based filtering

  • Document technology stack decisions in ADR 0004

  • Move bibliography files to contents directory structure


Diagram Walkthrough

flowchart LR
  BIB["references.bib<br/>in contents/"]
  YAML["custom_info.yaml<br/>in contents/"]
  PARSER["BibTeX & YAML<br/>Parsers"]
  MERGE["Merge<br/>Bibliography"]
  PAGE["Page Component<br/>+page.svelte"]
  SEARCH["Text & Tag<br/>Search"]
  UI["Styled UI<br/>Tailwind+DaisyUI"]
  
  BIB --> PARSER
  YAML --> PARSER
  PARSER --> MERGE
  MERGE --> PAGE
  PAGE --> SEARCH
  SEARCH --> UI
Loading

File Walkthrough

Relevant files
Documentation
3 files
0004-web-ui-technology-stack.md
Document SvelteKit and Cloudflare Pages selection               
+97/-0   
README.md
Document SvelteKit project setup instructions                       
+38/-0   
README.md
Document web development setup instructions                           
+9/-0     
Enhancement
13 files
+page.svelte
Build interactive bibliography display with search             
+343/-0 
+page.server.ts
Load and merge bibliography data on server                             
+24/-0   
bib.ts
Parse BibTeX entries with regex-based parser                         
+54/-0   
yaml.ts
Parse YAML custom metadata by site ID                                       
+33/-0   
merge.ts
Merge BibTeX entries with custom metadata                               
+15/-0   
author.ts
Format author names by language detection                               
+37/-0   
types.ts
Define TypeScript types for bibliography data                       
+42/-0   
app.html
Create SvelteKit root HTML template                                           
+11/-0   
app.d.ts
Define SvelteKit global type declarations                               
+13/-0   
+layout.svelte
Setup root layout with Tailwind styles                                     
+9/-0     
index.ts
Create library barrel export file                                               
+1/-0     
references.bib
Add two Japanese bibliography entries                                       
+18/-0   
ci.yml
Add path-filtered CI jobs for web application                       
+52/-1   
Configuration changes
9 files
package.json
Configure SvelteKit with Tailwind and DaisyUI                       
+32/-0   
svelte.config.js
Configure Cloudflare adapter for deployment                           
+18/-0   
vite.config.ts
Setup Vite with Tailwind and SvelteKit plugins                     
+5/-0     
tsconfig.json
Configure TypeScript for SvelteKit project                             
+20/-0   
layout.css
Import Tailwind and typography plugin                                       
+2/-0     
robots.txt
Add robots.txt for search engine crawling                               
+3/-0     
pnpm-workspace.yaml
Configure pnpm workspace settings                                               
+2/-0     
.npmrc
Enforce Node.js engine version requirement                             
+1/-0     
Makefile
Move bibliography files to contents directory                       
+4/-3     
Additional files
3 files
custom_info.yaml [link]   
references.bib +0/-9     
pnpm-lock.yaml +2286/-0

Summary by CodeRabbit

  • New Features

    • Interactive web bibliography with full-text search, tag-based filtering, autosuggest, and detailed entry display (authors, year, publisher, series, ISBN, tags, reviews).
    • Automatic generation of bibliography JSON for the web UI and robust client-side loading.
  • Documentation

    • Added development instructions for running the local web server.
  • Chores

    • CI and build flows adjusted for selective checks; content import logging and content directory support added.

✏️ Tip: You can customize this high-level summary in your review settings.

@Rindrics Rindrics closed this Jan 6, 2026
@Rindrics Rindrics reopened this Jan 6, 2026
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects bot commented Jan 6, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Path traversal risk

Description: Path traversal vulnerability: using join(process.cwd(), '..', 'contents') allows directory
traversal outside the intended directory structure, potentially exposing sensitive files.
+page.server.ts [10-10]

Referred Code
const contentsDir = join(process.cwd(), '..', 'contents');
ReDoS vulnerability

Description: Regular expression denial of service (ReDoS) vulnerability: the regex pattern
/@(\w+)\s*{\s*([^,]+)\s*,([^@]*)}/g with nested quantifiers and unbounded repetition can
cause catastrophic backtracking on malicious input.
bib.ts [11-14]

Referred Code
const entryRegex = /@(\w+)\s*\{\s*([^,]+)\s*,([^@]*)\}/g;
let match;

while ((match = entryRegex.exec(content)) !== null) {
Ticket Compliance
🟡
🎫 #3
🟢 Select frontend framework (React, Vue, Svelte, etc.)
Select hosting platform (GitHub Pages, Vercel, Cloudflare Pages, etc.)
Initialize project structure
Set up development environment
Create basic layout with search input
Add CI for frontend build
Consider using existing component libraries for rapid development
Frontend project scaffolding
Basic UI layout
Updated CI workflow
🔴 Static site generation preferred (per ADR-0002)
Must work with GitHub API for file operations
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing error handling: The BibTeX parser uses regex without handling malformed input or parsing failures that
could cause silent errors or unexpected behavior.

Referred Code
export function parseBibTeX(content: string): BibEntry[] {
	const entries: BibEntry[] = [];

	// Match BibTeX entries: @type{key, ... }
	const entryRegex = /@(\w+)\s*\{\s*([^,]+)\s*,([^@]*)\}/g;
	let match;

	while ((match = entryRegex.exec(content)) !== null) {
		const type = match[1].toLowerCase();
		const id = match[2].trim();
		const fieldsContent = match[3];

		const fields = parseFields(fieldsContent);

		entries.push({
			id,
			type,
			title: fields.title || '',
			author: fields.author || '',
			year: parseInt(fields.year || '0', 10),
			publisher: fields.publisher,


 ... (clipped 8 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Potential XSS vulnerability: User search input is directly interpolated into the UI without explicit sanitization,
which may expose the application to XSS if malicious content is rendered.

Referred Code
	bind:value={searchQuery}
	oninput={handleInput}
	onkeydown={handleKeydown}
	onblur={handleBlur}
	onfocus={() => (showTagSuggestions = isTagMode())}
	placeholder="検索... (#でタグ検索)"
	class="w-full rounded-xl border border-slate-700 bg-slate-800/80 py-3 pl-12 pr-4 text-slate-200 placeholder-slate-500 outline-none transition-all focus:border-amber-500/50 focus:ring-2 focus:ring-amber-500/20"
/>

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects bot commented Jan 6, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Improve BibTeX field parsing logic
Suggestion Impact:The commit directly implements the suggestion. The regex pattern was updated to support quoted values, nested braces, and bare words. The value extraction logic was modified to handle all three capture groups (braces, quotes, bare words), and added undefined checking with trim() for the value. The comments were also updated to reflect the new functionality.

code diff:

-	// Match field = value or field = {value}
-	const fieldRegex = /(\w+)\s*=\s*(?:\{([^}]*)\}|(\d+))/g;
+	// Match field = {value} or field = "value" or field = bareword
+	const fieldRegex = /\s*(\w+)\s*=\s*(?:\{((?:[^{}]|\{[^{}]*\})*)\}|"([^"]*)"|(\w+))\s*,?/gs;
 	let match;
 
 	while ((match = fieldRegex.exec(content)) !== null) {
 		const name = match[1].toLowerCase();
-		const value = match[2] ?? match[3];
-		fields[name] = value;
+		// value can be in braces (match[2]), quotes (match[3]), or be a bare word/number (match[4])
+		const value = match[2] ?? match[3] ?? match[4];
+		if (value !== undefined) {
+			fields[name] = value.trim();
+		}

Improve the BibTeX field parsing regular expression to support values enclosed
in double quotes and values containing nested curly braces. This change will
make the parser more robust and compatible with standard BibTeX formats.

web/src/lib/parsers/bib.ts [43-51]

-// Match field = value or field = {value}
-const fieldRegex = /(\w+)\s*=\s*(?:\{([^}]*)\}|(\d+))/g;
+// Match field = {value} or field = "value"
+const fieldRegex = /\s*(\w+)\s*=\s*(?:\{((?:[^{}]|\{[^{}]*\})*)\}|"([^"]*)"|(\w+))\s*,?/gs;
 let match;
 
 while ((match = fieldRegex.exec(content)) !== null) {
 	const name = match[1].toLowerCase();
-	const value = match[2] ?? match[3];
-	fields[name] = value;
+	// value can be in braces (match[2]), quotes (match[3]), or be a bare word/number (match[4])
+	const value = match[2] ?? match[3] ?? match[4];
+	if (value !== undefined) {
+		fields[name] = value.trim();
+	}
 }

[Suggestion processed]

Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies that the current fieldRegex is too simple and will fail on common valid BibTeX formats like quoted values or values with nested braces. The proposed regex and logic significantly improve the robustness of the parser, preventing potential data parsing errors.

Medium
Possible issue
Add file read error handling

Wrap the readFileSync calls in a try...catch block to handle potential errors
when reading bibliography files.

web/src/routes/+page.server.ts [10-13]

 const contentsDir = join(process.cwd(), '..', 'contents');
 
-const bibContent = readFileSync(join(contentsDir, 'references.bib'), 'utf-8');
-const yamlContent = readFileSync(join(contentsDir, 'custom_info.yaml'), 'utf-8');
+try {
+	const bibContent = readFileSync(join(contentsDir, 'references.bib'), 'utf-8');
+	const yamlContent = readFileSync(join(contentsDir, 'custom_info.yaml'), 'utf-8');
+} catch (error) {
+	console.error('Failed to read bibliography files:', error);
+	throw new Error('Bibliography files not found or inaccessible');
+}
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This suggestion correctly points out that synchronous file reads without error handling can crash the server, and adding a try...catch block significantly improves the application's robustness.

Medium
General
Remove arbitrary timeout delay
Suggestion Impact:The commit implements exactly what was suggested: removed the setTimeout wrapper from handleBlur (lines 4-9) and changed the onclick event to onmousedown on the suggestion button (line 18). This prevents the race condition between blur and click events.

code diff:

 	function handleBlur() {
-		// Delay to allow click on suggestion
-		setTimeout(() => {
-			showTagSuggestions = false;
-		}, 150);
+		showTagSuggestions = false;
 	}
 </script>
 
@@ -164,7 +161,7 @@
 						{#each tagSuggestions() as tag, index}
 							<button
 								type="button"
-								onclick={() => selectTag(tag)}
+								onmousedown={() => selectTag(tag)}

Remove the setTimeout from the handleBlur function and use the onmousedown event
on suggestion buttons to prevent a race condition.

web/src/routes/+page.svelte [95-100]

 function handleBlur() {
-	// Delay to allow click on suggestion
-	setTimeout(() => {
-		showTagSuggestions = false;
-	}, 150);
+	showTagSuggestions = false;
 }

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that using setTimeout in handleBlur is a fragile fix for a race condition and proposes a more robust solution, improving UI reliability.

Low
Fix tag selection race condition
Suggestion Impact:The suggestion was directly implemented. The commit changed onclick to onmousedown on line 18, and also removed the setTimeout workaround (lines 5-8) that was previously needed to handle the race condition, demonstrating that the suggestion successfully resolved the underlying issue.

code diff:

 	function handleBlur() {
-		// Delay to allow click on suggestion
-		setTimeout(() => {
-			showTagSuggestions = false;
-		}, 150);
+		showTagSuggestions = false;
 	}
 </script>
 
@@ -164,7 +161,7 @@
 						{#each tagSuggestions() as tag, index}
 							<button
 								type="button"
-								onclick={() => selectTag(tag)}
+								onmousedown={() => selectTag(tag)}

Change the onclick event handler on tag suggestion buttons to onmousedown to fix
a race condition with the input's onblur event.

web/src/routes/+page.svelte [164-175]

 {#each tagSuggestions() as tag, index}
 	<button
 		type="button"
-		onclick={() => selectTag(tag)}
+		onmousedown={() => selectTag(tag)}
 		class="rounded-lg px-3 py-1.5 text-sm transition-colors {index ===
 		selectedSuggestionIndex
 			? 'bg-amber-500/20 text-amber-200'
 			: 'border border-emerald-500/30 bg-emerald-500/10 text-emerald-400 hover:bg-emerald-500/20'}"
 	>
 		#{tag}
 	</button>
 {/each}

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a race condition between onclick and onblur and proposes using onmousedown as a standard and more reliable solution.

Low
  • Update

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Jan 6, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
kotetsu 0ee77bc Jan 06 2026, 01:27 PM

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

This PR splits CI into path-filtered jobs, moves bibliography content under contents/, updates the Makefile, and adds a new SvelteKit web/ app with data-generation scripts, parsers, types, UI pages, and build/config files.

Changes

Cohort / File(s) Summary
CI/CD Workflow Restructuring
.github/workflows/ci.yml
Replaced single check job with a changes job that outputs contents and web flags via path filters. Added conditional check-contents and check-web jobs to run format/lint or Node build/type-check respectively.
Makefile & Content relocation
Makefile, contents/references.bib, references.bib
Added CONTENTS_DIR and updated BIB_FILE/YAML_FILE to reference it. import target now iterates $(CONTENTS_DIR)/*.bibtex, echoes imported files. Moved/added bib entries into contents/references.bib and removed duplicate from root references.bib.
Documentation & ADR
README.md, docs/adr/0004-web-ui-technology-stack.md, web/README.md
Added "Development" instructions to top-level README, new project README for web/, and an ADR documenting chosen Web UI stack (SvelteKit, Cloudflare Pages, Tailwind/DaisyUI, Auth.js) with rationale and checklist.
Web project configuration
web/package.json, web/.npmrc, web/.gitignore, web/tsconfig.json, web/svelte.config.js, web/vite.config.ts
New SvelteKit project files: scripts, dependencies, strict TS config, Cloudflare adapter config, Vite plugins (Tailwind + SvelteKit), npm engine-strict, and .gitignore entries.
Data generation & tooling
web/scripts/generate-data.ts, web/static/robots.txt
New Node script parses BibTeX and YAML, merges metadata, and emits static/data/bibliography.json. Added permissive robots.txt.
Parsers, merge, formatters, types
web/src/lib/parsers/bib.ts, web/src/lib/parsers/yaml.ts, web/src/lib/merge.ts, web/src/lib/formatters/author.ts, web/src/lib/types.ts, web/src/lib/index.ts
Added regex-based BibTeX parser, YAML custom-info parser with site scoping, merge utility, language-aware author formatter (Japanese handling), and TypeScript interfaces (BibEntry, CustomInfo, BibliographyItem, CustomInfoYaml).
SvelteKit app scaffolding & templates
web/src/app.d.ts, web/src/app.html, web/src/routes/+layout.svelte, web/src/routes/layout.css
Added SvelteKit app declaration, minimal HTML template, layout component injecting favicon, and Tailwind/typography-enabled layout CSS.
Bibliography page & loader
web/src/routes/+page.ts, web/src/routes/+page.svelte
New prerendered page that loads /data/bibliography.json with error handling; UI supports text and tag searches (leading #), tag suggestions, keyboard navigation, and item rendering (authors, tags, reviews, links).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇
I hopped through folders, found a trail,
Moved references, tuned the tail;
Svelte spins pages, data sings,
Tags and names and tiny things;
A little rabbit cheers—build brings spring.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'docs (adr): select SvelteKit on Cloudflare' accurately reflects the primary change: documenting the technology decision (ADR 0004) to select SvelteKit and Cloudflare for the web UI. While the PR includes substantial implementation changes beyond documentation, the title correctly identifies the main decision point and documentation artifact being introduced.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ee77bc and 62457cf.

📒 Files selected for processing (3)
  • web/scripts/generate-data.ts
  • web/src/lib/parsers/yaml.ts
  • web/src/routes/+page.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/src/routes/+page.ts
  • web/scripts/generate-data.ts
🧰 Additional context used
🧬 Code graph analysis (1)
web/src/lib/parsers/yaml.ts (1)
web/src/lib/types.ts (1)
  • CustomInfo (19-22)
🔇 Additional comments (1)
web/src/lib/parsers/yaml.ts (1)

20-66: Excellent implementation—previous concerns fully addressed.

The function now includes comprehensive error handling and validation:

  • Try-catch around YAML parsing with graceful fallback
  • Multi-level structure validation using the isPlainObject helper
  • Field-level validation for both tags and review before insertion

The implementation is robust, handles edge cases well, and maintains type safety with properly guarded assertions.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Fix all issues with AI Agents
In @README.md:
- Line 11: Update the README text that currently reads "Start local sever" to
the correct phrase "Start local server" (locate and replace the string "Start
local sever" in the README.md content).

In @web/package.json:
- Line 11: The package.json "prepare" script currently appends "|| echo ''"
which silently swallows failures from "svelte-kit sync"; remove the fallback so
failures surface during install/CI (i.e., change the "prepare" script to run
"svelte-kit sync" only) or, if you need to tolerate specific known errors,
replace the silent fallback with explicit handling and a clear comment
explaining why (refer to the "prepare" script entry in package.json).

In @web/scripts/generate-data.ts:
- Around line 16-44: The interfaces BibEntry, CustomInfo, CustomInfoYaml and
BibliographyItem are duplicated here; remove these local definitions and import
the same types from the central types module instead. Replace the local
interface declarations with a single import that brings in BibEntry, CustomInfo,
CustomInfoYaml and BibliographyItem, then update any references to use those
imported types (e.g., BibliographyItem in functions or variables) so the file
relies on the single source-of-truth types.
- Around line 92-110: The parseCustomInfo function in generate-data.ts
duplicates logic that exists in the YAML parser module; remove this local
implementation and import and use the shared parser instead. Replace the local
parseCustomInfo with an import of the canonical parser function from the YAML
parser module and call that function (preserving the same signature and return
type), ensuring any used helpers like parseYaml are referenced from the imported
module or removed if redundant. Update references in this file to use the
imported parseCustomInfo symbol so there is a single source of truth for YAML
parsing.
- Around line 112-121: Remove the local mergeBibliography implementation and
import the shared merge function from web/src/lib/merge.ts instead; replace the
function declaration for mergeBibliography with an import (e.g., import {
mergeBibliography } from "web/src/lib/merge") and ensure any types (BibEntry,
CustomInfo, BibliographyItem) referenced by callers match the exported types
from the shared module, updating the import list at the top of the file and
deleting the duplicated function body.
- Around line 46-90: The file duplicates the BibTeX parsing logic (functions
parseBibTeX and parseFields); remove these local function definitions and
instead import the shared parser (exporting parseBibTeX and parseFields and any
needed types like BibEntry) from the central parser module, update usages to use
the imported parseBibTeX, and ensure the import is added at the top and types
are referenced correctly so there is no duplicate implementation to maintain.

In @web/src/lib/parsers/yaml.ts:
- Around line 12-32: parseCustomInfo currently calls parse(...) with a type
assertion to CustomInfoYaml and no try/catch or runtime checks; wrap the call to
parse in a try/catch to handle malformed YAML (return an empty Map and log the
error), then perform runtime validation of the parsed value before using it:
ensure parsed is a plain object, iterate Object.entries(parsed) only when each
value is an object, check that sites[siteId] exists and that its tags and review
fields are the expected types (e.g., tags is an array or undefined, review is a
string or undefined) before calling result.set; keep function signature and
return an empty Map on invalid inputs to preserve callers.

In @web/src/routes/+page.svelte:
- Around line 12-22: The $derived usage for allTags is wrapped in a function
which is invalid in Svelte 5; replace the function wrapper with a direct
expression or use $derived.by for a body-based derivation. Update the allTags
declaration to compute from data.items and item.customInfo?.tags directly via
$derived (or $derived.by if you need the current multi-line loop semantics) so
the returned value is an array of sorted unique tags.
- Around line 37-64: The computed store filteredItems is using $derived(...) but
for complex derivations you must call $derived.by(...); replace the $derived(()
=> { ... }) invocation that defines filteredItems with $derived.by(() => { ...
}) while keeping the same callback body and any dependencies/imports intact so
the tag-mode and normal search branches (references: filteredItems, isTagMode,
tagQuery, searchQuery, data.items) continue to work.
- Around line 31-35: The $derived call for tagSuggestions uses Svelte 4 syntax
and must be replaced with Svelte 5 derived usage: create tagSuggestions with the
derived helper (not $derived) and list the dependent stores (isTagMode,
tagQuery, allTags) as inputs, then use the callback that receives their values
(e.g., ([$isTagMode, $tagQuery, $allTags]) => { if (!$isTagMode) return [];
return $allTags.filter(tag => tag.toLowerCase().includes($tagQuery)); }); also
update any calls to allTags(), isTagMode(), and tagQuery() to use the store
values passed into the derived callback (or their $-prefixed store values if
used elsewhere) so the dependency and reactive behavior are correct.

In @web/src/routes/+page.ts:
- Around line 5-12: The load function currently calls
fetch('/data/bibliography.json') without handling network or HTTP errors; wrap
the fetch+json logic in a try/catch inside export async function load({ fetch })
to detect fetch failures and non-ok responses (check response.ok and throw with
response.status), log the error (console.error or a logger) and return a safe
fallback like { items: [] } so prerendering/runtime won’t crash; reference the
load function, the fetch('/data/bibliography.json') call and the
BibliographyItem typing when adding the error handling and fallback.
🧹 Nitpick comments (9)
web/static/robots.txt (1)

1-3: robots.txt is correctly configured for a public bibliography site.

The permissive policy is appropriate for a documentation/bibliography site where search engine discoverability is valuable. The file syntax is valid and will allow crawlers to index all content without restriction.

Optional enhancement: Consider adding a Sitemap directive to help search engines discover and prioritize your content more efficiently. If you add a sitemap in the future, update this file with:

Sitemap: https://yourdomain.com/sitemap.xml
web/README.md (1)

1-38: Consider documenting bibliography-specific workflows.

The README is standard SvelteKit boilerplate. For better onboarding, consider adding sections that document:

  • Data file generation (the web/scripts/generate-data.ts prebuild step mentioned in the PR)
  • Local development workflow with bibliography content sourced from contents/ directory
  • Any custom npm scripts relevant to bibliography management

This helps future maintainers understand the data flow beyond generic SvelteKit commands.

web/src/lib/index.ts (1)

1-1: Consider re-exporting commonly used library utilities.

The $lib index serves as a convenient entry point. Per the PR, multiple utility modules exist (types, parsers, merge logic, author formatting). Consider adding re-exports for frequently imported utilities to improve discoverability and reduce import paths:

// Example: Re-export common utilities for easier access
export * from './types.js';
export { mergeCustomInfo } from './merge.js';
export { formatAuthor } from './formatters/author.js';

This is optional but improves developer ergonomics and makes the public API clearer.

contents/references.bib (1)

3-3: Standardize author field formatting for consistent parsing.

The author fields use inconsistent spacing:

  • Line 3: {甲野,善紀} (no space after comma)
  • Line 12: {諏訪, 正樹} (space after comma)

Standardize to one format to ensure predictable parsing behavior, especially given the romaji-enabled author search feature mentioned in the PR objectives.

🔎 Recommended fix (standardize with space after comma)
-  author = {甲野,善紀},
+  author = {甲野, 善紀},

Also applies to: 12-12

web/src/lib/parsers/bib.ts (2)

37-58: Nested braces support is limited to one level.

The regex pattern on line 45 only handles a single level of nested braces. For example:

  • ✓ Works: {text with {nested} braces}
  • ✗ Fails: {text with {deeply {nested}} braces}

The comment on line 39 says "including nested" but doesn't specify depth. Most BibTeX files use single-level nesting, so this is likely sufficient for typical use cases.

If deeper nesting is needed, consider using a proper parser library like biblatex-csl-converter or implementing a recursive brace matcher. However, for the current scope, this limitation is acceptable.


21-31: Consider validating required fields.

The parser defaults missing title and author to empty strings (lines 24-25) and year to 0 (line 26). While this prevents runtime errors, bibliography entries with missing required fields might not be useful.

Consider adding validation or warnings for entries with missing required metadata:

🔎 Optional validation approach
 		const fields = parseFields(fieldsContent);
+		
+		// Optionally validate required fields
+		if (!fields.title || !fields.author) {
+			console.warn(`Entry ${id} is missing required fields:`, { 
+				hasTitle: !!fields.title, 
+				hasAuthor: !!fields.author 
+			});
+		}
 
 		entries.push({
 			id,
 			type,
 			title: fields.title || '',
 			author: fields.author || '',
 			year: parseInt(fields.year || '0', 10),
 			publisher: fields.publisher,
 			series: fields.series,
 			isbn: fields.isbn,
 			url: fields.url
 		});
web/src/lib/formatters/author.ts (1)

4-7: CJK character range is incomplete.

The regex on line 6 uses \u4E00-\u9FAF for CJK Unified Ideographs, but the complete range is \u4E00-\u9FFF. Characters in the range U+9FB0–U+9FFF (160 characters) will not be detected as Japanese.

🔎 Recommended fix to complete the CJK range
 function isJapanese(name: string): boolean {
 	// Match Hiragana, Katakana, and CJK Unified Ideographs
-	return /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/.test(name);
+	return /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF]/.test(name);
 }

For more comprehensive coverage, consider including CJK extensions:

return /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u3400-\u4DBF]/.test(name);
web/src/routes/+page.svelte (2)

25-28: Simplify derived expressions—use direct expressions instead of function wrappers.

These simple derivations should use direct expressions rather than function wrappers.

🔎 Proposed refactor
-	const isTagMode = $derived(() => searchQuery.startsWith('#'));
+	const isTagMode = $derived(searchQuery.startsWith('#'));

-	const tagQuery = $derived(() => (isTagMode() ? searchQuery.slice(1).toLowerCase() : ''));
+	const tagQuery = $derived(isTagMode ? searchQuery.slice(1).toLowerCase() : '');

After this change, you'll need to update usages from isTagMode() to isTagMode and tagQuery() to tagQuery throughout the file.


104-177: Header search UI is well-implemented with proper keyboard and mouse interaction.

The search input includes:

  • Visual search icon and clear button
  • Tag mode detection and suggestions
  • Keyboard navigation (arrows, Enter, Escape)
  • Proper event timing (mousedown vs click) to avoid blur conflicts

Tailwind v4 syntax is correctly used throughout.

Optional accessibility enhancement: Consider adding aria-label attributes to the search input and clear button, plus role="listbox" and role="option" for the tag suggestions dropdown to improve screen reader support.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 373752f and 0ee77bc.

⛔ Files ignored due to path filters (2)
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • web/src/lib/assets/favicon.svg is excluded by !**/*.svg
📒 Files selected for processing (28)
  • .github/workflows/ci.yml
  • Makefile
  • README.md
  • contents/custom_info.yaml
  • contents/references.bib
  • docs/adr/0004-web-ui-technology-stack.md
  • references.bib
  • web/.gitignore
  • web/.npmrc
  • web/README.md
  • web/package.json
  • web/scripts/generate-data.ts
  • web/src/app.d.ts
  • web/src/app.html
  • web/src/lib/formatters/author.ts
  • web/src/lib/index.ts
  • web/src/lib/merge.ts
  • web/src/lib/parsers/bib.ts
  • web/src/lib/parsers/yaml.ts
  • web/src/lib/types.ts
  • web/src/routes/+layout.svelte
  • web/src/routes/+page.svelte
  • web/src/routes/+page.ts
  • web/src/routes/layout.css
  • web/static/robots.txt
  • web/svelte.config.js
  • web/tsconfig.json
  • web/vite.config.ts
💤 Files with no reviewable changes (1)
  • references.bib
🧰 Additional context used
🧬 Code graph analysis (5)
web/src/lib/parsers/bib.ts (1)
web/src/lib/types.ts (1)
  • BibEntry (4-14)
web/src/lib/merge.ts (1)
web/src/lib/types.ts (3)
  • BibEntry (4-14)
  • CustomInfo (19-22)
  • BibliographyItem (27-29)
web/src/routes/+page.ts (1)
web/src/lib/types.ts (1)
  • BibliographyItem (27-29)
web/src/lib/parsers/yaml.ts (1)
web/src/lib/types.ts (2)
  • CustomInfo (19-22)
  • CustomInfoYaml (34-41)
web/scripts/generate-data.ts (4)
web/src/lib/types.ts (4)
  • BibEntry (4-14)
  • CustomInfo (19-22)
  • CustomInfoYaml (34-41)
  • BibliographyItem (27-29)
web/src/lib/parsers/bib.ts (1)
  • parseBibTeX (7-35)
web/src/lib/parsers/yaml.ts (1)
  • parseCustomInfo (12-32)
web/src/lib/merge.ts (1)
  • mergeBibliography (6-14)
🪛 checkmake (0.2.2)
Makefile

[warning] 9-9: Target body for "import" exceeds allowed length of 5 (7).

(maxbodylength)


[warning] 7-7: Missing required phony target "all"

(minphony)


[warning] 7-7: Missing required phony target "test"

(minphony)

🪛 LanguageTool
README.md

[grammar] ~11-~11: Ensure spelling is correct
Context: ...ation key. ## Development Start local sever bash cd web pnpm run dev

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Workers Builds: kotetsu
🔇 Additional comments (20)
web/.npmrc (1)

1-1: Enforce Node.js/npm version compatibility.

Enabling engine-strict=true ensures that developers and CI pipelines enforce compatible Node.js and npm versions, improving build reproducibility and preventing version-mismatch surprises. Good practice.

web/src/app.html (1)

1-11: LGTM!

Standard SvelteKit app template with proper viewport meta, preload data strategy, and placeholder hooks.

web/src/routes/layout.css (1)

1-2: LGTM!

Correct Tailwind CSS v4 syntax using @import 'tailwindcss' and @plugin directive for the typography plugin.

web/svelte.config.js (1)

1-18: LGTM!

Clean SvelteKit configuration with Cloudflare adapter and Vite preprocessing, aligning with the ADR decision to deploy on Cloudflare Pages.

web/tsconfig.json (1)

1-21: LGTM!

Well-configured TypeScript setup extending SvelteKit's generated config with strict mode enabled and appropriate compiler options for a bundler-based project.

web/vite.config.ts (1)

1-5: LGTM!

Correct Vite configuration with Tailwind CSS v4 plugin and SvelteKit integration. Plugin order is appropriate.

web/src/app.d.ts (1)

1-13: LGTM!

Standard SvelteKit type declaration file with placeholder interfaces ready for customization as needed.

.github/workflows/ci.yml (2)

10-27: Well-structured path-based filtering.

The use of dorny/paths-filter to conditionally run jobs based on changed paths is a good approach for CI efficiency. The separation of contents and web filters allows targeted validation.


41-70: No lint script is configured in the web project.

The web project does not have a lint script, nor does it have any linting tools (ESLint, Prettier, etc.) in its dependencies. The existing scripts only include type checking via svelte-check and build steps. The reference to check-contents is not relevant as it handles markdown/content files via a Makefile, not JavaScript/Svelte linting.

Likely an incorrect or invalid review comment.

web/src/routes/+layout.svelte (1)

1-9: LGTM!

The layout component correctly uses Svelte 5 syntax with $props() and {@render children()}, properly imports global styles and assets, and injects the favicon into the document head.

web/.gitignore (1)

1-26: LGTM!

The ignore patterns are comprehensive and follow standard practices for a SvelteKit project. The inclusion of /static/data/ correctly ignores the generated bibliography data directory.

web/src/lib/merge.ts (1)

6-14: LGTM! Clean merge implementation.

The function correctly combines BibTeX entries with custom metadata. The use of Map.get() safely handles missing entries by returning undefined, which aligns with the optional customInfo field in BibliographyItem.

docs/adr/0004-web-ui-technology-stack.md (1)

1-97: ADR looks well-structured and aligns with the PR implementation.

The architecture decision record clearly documents the rationale for selecting SvelteKit, Cloudflare Pages, Tailwind + DaisyUI, and Auth.js. The decision tables, consequences, and implementation notes are comprehensive and match the actual technology choices seen in the PR (svelte.config.js using Cloudflare adapter, Tailwind configuration, etc.).

web/src/lib/types.ts (1)

1-42: LGTM! Type definitions are well-documented and properly structured.

The type hierarchy clearly separates concerns:

  • BibEntry for parsed BibTeX data
  • CustomInfo for blog-specific metadata
  • BibliographyItem as the merged result
  • CustomInfoYaml for the raw YAML structure

These types align with their usage across the parsing, merging, and data generation pipeline.

Makefile (1)

1-16: LGTM! Makefile correctly adapted to the new contents/ directory structure.

The changes properly update file paths to use CONTENTS_DIR and the import target now processes .bibtex files from the correct location with helpful logging.

web/scripts/generate-data.ts (1)

123-145: Main function orchestrates data generation correctly.

The main() function properly reads the source files, invokes parsing and merging, ensures output directory existence, and writes the final JSON with helpful logging.

web/src/routes/+page.svelte (4)

66-98: Event handlers implement correct keyboard navigation and suggestion interaction.

The handlers properly manage:

  • Tag selection with state reset
  • Arrow key navigation with boundary checks
  • Enter key selection and Escape dismissal
  • Suggestion visibility tied to tag mode
  • Using onmousedown instead of onclick to prevent blur race conditions

After fixing the $derived syntax issues, update the function calls in these handlers (e.g., isTagMode()isTagMode, tagSuggestions()tagSuggestions).


179-318: Bibliography item display is well-structured with proper styling and accessibility.

The card layout effectively presents:

  • Entry metadata (type, title, author, year, publisher, series)
  • Custom info (tags, review)
  • External links with secure attributes (rel="noopener noreferrer")

Tailwind v4 classes are correctly applied. After fixing the $derived syntax, update filteredItems() to filteredItems in the loop (line 181).


320-328: Empty state handling provides clear user feedback.

Appropriate messages for both search-no-results and no-data scenarios. After fixing the $derived syntax, update filteredItems() to filteredItems here as well.


331-340: Footer displays helpful item counts.

Clear indication of filtered vs total items when search is active. Update filteredItems() to filteredItems after fixing the $derived syntax.


## Development

Start local sever
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix typo in documentation.

"Start local sever" should be "Start local server".

🔎 Proposed fix
-Start local sever
+Start local server
🧰 Tools
🪛 LanguageTool

[grammar] ~11-~11: Ensure spelling is correct
Context: ...ation key. ## Development Start local sever bash cd web pnpm run dev

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
In @README.md at line 11, Update the README text that currently reads "Start
local sever" to the correct phrase "Start local server" (locate and replace the
string "Start local sever" in the README.md content).

"dev": "pnpm generate-data && vite dev --port 30500 --open",
"build": "pnpm generate-data && vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid suppressing errors in the prepare script.

The || echo '' fallback silently swallows errors from svelte-kit sync, which could hide legitimate setup issues during package installation or in CI/CD environments.

🔎 Recommended fix
-		"prepare": "svelte-kit sync || echo ''",
+		"prepare": "svelte-kit sync",

If svelte-kit sync legitimately fails in some scenarios and you need to handle that gracefully, document why the fallback is necessary or use a more explicit error handling approach.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"prepare": "svelte-kit sync || echo ''",
"prepare": "svelte-kit sync",
🤖 Prompt for AI Agents
In @web/package.json at line 11, The package.json "prepare" script currently
appends "|| echo ''" which silently swallows failures from "svelte-kit sync";
remove the fallback so failures surface during install/CI (i.e., change the
"prepare" script to run "svelte-kit sync" only) or, if you need to tolerate
specific known errors, replace the silent fallback with explicit handling and a
clear comment explaining why (refer to the "prepare" script entry in
package.json).

Comment on lines +46 to +90
// Parse BibTeX content
function parseBibTeX(content: string): BibEntry[] {
const entries: BibEntry[] = [];
const entryRegex = /@(\w+)\s*\{\s*([^,]+)\s*,([^@]*)\}/g;
let match;

while ((match = entryRegex.exec(content)) !== null) {
const type = match[1].toLowerCase();
const id = match[2].trim();
const fieldsContent = match[3];
const fields = parseFields(fieldsContent);

entries.push({
id,
type,
title: fields.title || '',
author: fields.author || '',
year: parseInt(fields.year || '0', 10),
publisher: fields.publisher,
series: fields.series,
isbn: fields.isbn,
url: fields.url
});
}

return entries;
}

function parseFields(content: string): Record<string, string> {
const fields: Record<string, string> = {};
// Match field = {value} or field = "value" or field = bareword
const fieldRegex = /\s*(\w+)\s*=\s*(?:\{((?:[^{}]|\{[^{}]*\})*)\}|"([^"]*)"|(\w+))\s*,?/gs;
let match;

while ((match = fieldRegex.exec(content)) !== null) {
const name = match[1].toLowerCase();
// value can be in braces (match[2]), quotes (match[3]), or be a bare word/number (match[4])
const value = match[2] ?? match[3] ?? match[4];
if (value !== undefined) {
fields[name] = value.trim();
}
}

return fields;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Eliminate parser duplication—import from web/src/lib/parsers/bib.ts.

The parseBibTeX and parseFields functions are duplicated from web/src/lib/parsers/bib.ts. Import the parser instead to avoid maintaining duplicate code.

🔎 Proposed refactor
+import { parseBibTeX } from '../src/lib/parsers/bib.js';
+
-// Parse BibTeX content
-function parseBibTeX(content: string): BibEntry[] {
-	const entries: BibEntry[] = [];
-	const entryRegex = /@(\w+)\s*\{\s*([^,]+)\s*,([^@]*)\}/g;
-	let match;
-
-	while ((match = entryRegex.exec(content)) !== null) {
-		const type = match[1].toLowerCase();
-		const id = match[2].trim();
-		const fieldsContent = match[3];
-		const fields = parseFields(fieldsContent);
-
-		entries.push({
-			id,
-			type,
-			title: fields.title || '',
-			author: fields.author || '',
-			year: parseInt(fields.year || '0', 10),
-			publisher: fields.publisher,
-			series: fields.series,
-			isbn: fields.isbn,
-			url: fields.url
-		});
-	}
-
-	return entries;
-}
-
-function parseFields(content: string): Record<string, string> {
-	const fields: Record<string, string> = {};
-	// Match field = {value} or field = "value" or field = bareword
-	const fieldRegex = /\s*(\w+)\s*=\s*(?:\{((?:[^{}]|\{[^{}]*\})*)\}|"([^"]*)"|(\w+))\s*,?/gs;
-	let match;
-
-	while ((match = fieldRegex.exec(content)) !== null) {
-		const name = match[1].toLowerCase();
-		// value can be in braces (match[2]), quotes (match[3]), or be a bare word/number (match[4])
-		const value = match[2] ?? match[3] ?? match[4];
-		if (value !== undefined) {
-			fields[name] = value.trim();
-		}
-	}
-
-	return fields;
-}
🤖 Prompt for AI Agents
In @web/scripts/generate-data.ts around lines 46 - 90, The file duplicates the
BibTeX parsing logic (functions parseBibTeX and parseFields); remove these local
function definitions and instead import the shared parser (exporting parseBibTeX
and parseFields and any needed types like BibEntry) from the central parser
module, update usages to use the imported parseBibTeX, and ensure the import is
added at the top and types are referenced correctly so there is no duplicate
implementation to maintain.

Comment on lines +12 to +22
const allTags = $derived(() => {
const tags = new Set<string>();
for (const item of data.items) {
if (item.customInfo?.tags) {
for (const tag of item.customInfo.tags) {
tags.add(tag);
}
}
}
return Array.from(tags).sort();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Simplify $derived syntax—remove unnecessary function wrapper.

In Svelte 5, $derived expects an expression or block directly, not a function. The current $derived(() => {...}) pattern is incorrect.

🔎 Proposed fix
-	const allTags = $derived(() => {
+	const allTags = $derived.by(() => {
 		const tags = new Set<string>();
 		for (const item of data.items) {
 			if (item.customInfo?.tags) {
@@ -20,5 +20,5 @@
 			}
 		}
 		return Array.from(tags).sort();
 	});

Note: Use $derived.by() for complex derivations that require a function body. Alternatively, if the computation can be expressed as a single expression, use $derived(expression).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const allTags = $derived(() => {
const tags = new Set<string>();
for (const item of data.items) {
if (item.customInfo?.tags) {
for (const tag of item.customInfo.tags) {
tags.add(tag);
}
}
}
return Array.from(tags).sort();
});
const allTags = $derived.by(() => {
const tags = new Set<string>();
for (const item of data.items) {
if (item.customInfo?.tags) {
for (const tag of item.customInfo.tags) {
tags.add(tag);
}
}
}
return Array.from(tags).sort();
});
🤖 Prompt for AI Agents
In @web/src/routes/+page.svelte around lines 12 - 22, The $derived usage for
allTags is wrapped in a function which is invalid in Svelte 5; replace the
function wrapper with a direct expression or use $derived.by for a body-based
derivation. Update the allTags declaration to compute from data.items and
item.customInfo?.tags directly via $derived (or $derived.by if you need the
current multi-line loop semantics) so the returned value is an array of sorted
unique tags.

Comment on lines +31 to +35
const tagSuggestions = $derived(() => {
if (!isTagMode()) return [];
const query = tagQuery();
return allTags().filter((tag) => tag.toLowerCase().includes(query));
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix $derived usage for tagSuggestions.

Similar to allTags, this needs the correct Svelte 5 syntax.

🔎 Proposed fix
-	const tagSuggestions = $derived(() => {
-		if (!isTagMode()) return [];
-		const query = tagQuery();
+	const tagSuggestions = $derived.by(() => {
+		if (!isTagMode) return [];
+		const query = tagQuery;
 		return allTags().filter((tag) => tag.toLowerCase().includes(query));
 	});

Note: After fixing the isTagMode and tagQuery syntax as suggested above, update references here as well. Also update calls to allTags() if you refactor that too.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @web/src/routes/+page.svelte around lines 31 - 35, The $derived call for
tagSuggestions uses Svelte 4 syntax and must be replaced with Svelte 5 derived
usage: create tagSuggestions with the derived helper (not $derived) and list the
dependent stores (isTagMode, tagQuery, allTags) as inputs, then use the callback
that receives their values (e.g., ([$isTagMode, $tagQuery, $allTags]) => { if
(!$isTagMode) return []; return $allTags.filter(tag =>
tag.toLowerCase().includes($tagQuery)); }); also update any calls to allTags(),
isTagMode(), and tagQuery() to use the store values passed into the derived
callback (or their $-prefixed store values if used elsewhere) so the dependency
and reactive behavior are correct.

Comment on lines +37 to +64
const filteredItems = $derived(() => {
if (!searchQuery.trim()) return data.items;

// Tag search mode
if (isTagMode()) {
const query = tagQuery();
if (!query) return data.items;
return data.items.filter((item) =>
item.customInfo?.tags?.some((tag) => tag.toLowerCase().includes(query))
);
}

// Normal search mode
const query = searchQuery.toLowerCase();
return data.items.filter((item) => {
// Search in entry ID (for romanized author names)
if (item.id.toLowerCase().includes(query)) return true;
// Search in title
if (item.title.toLowerCase().includes(query)) return true;
// Search in author
if (item.author.toLowerCase().includes(query)) return true;
// Search in tags
if (item.customInfo?.tags?.some((tag) => tag.toLowerCase().includes(query))) return true;
// Search in review
if (item.customInfo?.review?.toLowerCase().includes(query)) return true;
return false;
});
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix $derived usage for filteredItems.

Same issue—use $derived.by() for complex derivations.

🔎 Proposed fix
-	const filteredItems = $derived(() => {
+	const filteredItems = $derived.by(() => {
 		if (!searchQuery.trim()) return data.items;
 
 		// Tag search mode
-		if (isTagMode()) {
-			const query = tagQuery();
+		if (isTagMode) {
+			const query = tagQuery;
 			if (!query) return data.items;
 			return data.items.filter((item) =>
 				item.customInfo?.tags?.some((tag) => tag.toLowerCase().includes(query))
 			);
 		}
 
 		// Normal search mode
 		const query = searchQuery.toLowerCase();
 		return data.items.filter((item) => {
 			// Search in entry ID (for romanized author names)
 			if (item.id.toLowerCase().includes(query)) return true;
 			// Search in title
 			if (item.title.toLowerCase().includes(query)) return true;
 			// Search in author
 			if (item.author.toLowerCase().includes(query)) return true;
 			// Search in tags
 			if (item.customInfo?.tags?.some((tag) => tag.toLowerCase().includes(query))) return true;
 			// Search in review
 			if (item.customInfo?.review?.toLowerCase().includes(query)) return true;
 			return false;
 		});
 	});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const filteredItems = $derived(() => {
if (!searchQuery.trim()) return data.items;
// Tag search mode
if (isTagMode()) {
const query = tagQuery();
if (!query) return data.items;
return data.items.filter((item) =>
item.customInfo?.tags?.some((tag) => tag.toLowerCase().includes(query))
);
}
// Normal search mode
const query = searchQuery.toLowerCase();
return data.items.filter((item) => {
// Search in entry ID (for romanized author names)
if (item.id.toLowerCase().includes(query)) return true;
// Search in title
if (item.title.toLowerCase().includes(query)) return true;
// Search in author
if (item.author.toLowerCase().includes(query)) return true;
// Search in tags
if (item.customInfo?.tags?.some((tag) => tag.toLowerCase().includes(query))) return true;
// Search in review
if (item.customInfo?.review?.toLowerCase().includes(query)) return true;
return false;
});
});
const filteredItems = $derived.by(() => {
if (!searchQuery.trim()) return data.items;
// Tag search mode
if (isTagMode) {
const query = tagQuery;
if (!query) return data.items;
return data.items.filter((item) =>
item.customInfo?.tags?.some((tag) => tag.toLowerCase().includes(query))
);
}
// Normal search mode
const query = searchQuery.toLowerCase();
return data.items.filter((item) => {
// Search in entry ID (for romanized author names)
if (item.id.toLowerCase().includes(query)) return true;
// Search in title
if (item.title.toLowerCase().includes(query)) return true;
// Search in author
if (item.author.toLowerCase().includes(query)) return true;
// Search in tags
if (item.customInfo?.tags?.some((tag) => tag.toLowerCase().includes(query))) return true;
// Search in review
if (item.customInfo?.review?.toLowerCase().includes(query)) return true;
return false;
});
});
🤖 Prompt for AI Agents
In @web/src/routes/+page.svelte around lines 37 - 64, The computed store
filteredItems is using $derived(...) but for complex derivations you must call
$derived.by(...); replace the $derived(() => { ... }) invocation that defines
filteredItems with $derived.by(() => { ... }) while keeping the same callback
body and any dependencies/imports intact so the tag-mode and normal search
branches (references: filteredItems, isTagMode, tagQuery, searchQuery,
data.items) continue to work.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying kotetsu with  Cloudflare Pages  Cloudflare Pages

Latest commit: 62457cf
Status: ✅  Deploy successful!
Preview URL: https://ec70fd74.kotetsu.pages.dev
Branch Preview URL: https://issue-3-web-ui.kotetsu.pages.dev

View logs

@Rindrics Rindrics merged commit 90fd260 into main Jan 6, 2026
6 checks passed
@Rindrics Rindrics deleted the issue-3-web-ui branch January 6, 2026 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant