Skip to content

Extract framework-agnostic lesson logic into @spelling-creator/core - #16

Merged
playforge-coding merged 1 commit into
masterfrom
core-extract-leaves
Aug 5, 2026
Merged

Extract framework-agnostic lesson logic into @spelling-creator/core#16
playforge-coding merged 1 commit into
masterfrom
core-extract-leaves

Conversation

@playforge-coding

@playforge-coding playforge-coding commented Aug 5, 2026

Copy link
Copy Markdown
Owner

First layer of splitting the lesson model out of apps/web/src/lib so the Worker and the MCP server can reuse it.

Adds packages/core and moves the tier that has no React dependency and no imports back into the app. All eight moves are pure renames — no content changed — so the reviewable diff is the import rewrites and the package wiring.

Shape

No build step, no barrel entry: every module is its own subpath export, matching the convention @spelling-creator/mcp already uses. That is what keeps browser-only modules out of the Worker's import graph — importing /questions can never drag in something that touches document.

The lint rule is the interesting part

Two of the moved modules (image, jsonExport) still reach for the DOM. Rather than grant core the browser env wholesale, it is linted against the narrower worker env with those two files opted back into browser by name. So anything added to core later that reaches for a browser global fails lint instead of breaking inside the Worker. They move behind a /browser subpath when the rest of that tier is extracted.

Deliberately left behind

  • imageRef.js (pulls in imageStore.js / IndexedDB) and htmlPreview.js (pulls in docx/mammoth) — browser tier, not leaf tier.
  • spellingWords.js — reads import.meta.env.VITE_API_URL at module scope, so it breaks outside an rsbuild build. Needs a refactor, not a move.
  • i18n.js / languages.js — coupled to react-i18next.

fuse.js moves with lessonSearch to core's dependencies.

Also here

A separate commit adds .agents/skills/** to oxfmt's ignore patterns. That break is pre-existing on master (vendored skill content is formatted upstream) and would otherwise fail CI for every PR in this stack.

Verification

pnpm run lint, pnpm run build, pnpm run build:docs, and all three suites — web 15, api 27, mcp 14 — pass.

Stack created with GitHub Stacks CLIGive Feedback 💬

Summary by CodeRabbit

  • New Features
    • Added lesson JSON import and export, including validation, normalization, and helpful error handling.
    • Improved image handling with resizing, format optimization, and consistent display settings.
    • Added fuzzy lesson search across titles and authors.
    • Standardized question and spelling block creation for consistent editing behavior.
  • Documentation
    • Expanded documentation for the monorepo structure, lesson capabilities, deployment, and shared functionality.
  • Chores
    • Updated linting and deployment configuration.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c6f0577-55b6-4f52-9e2f-9084e7965398

📥 Commits

Reviewing files that changed from the base of the PR and between c3cc53d and 4b7e0e1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (29)
  • .github/workflows/deploy.yml
  • .oxlintrc.json
  • README.md
  • apps/docs/docs/monorepo/getting-started.md
  • apps/docs/docs/monorepo/overview.md
  • apps/docs/docs/web-app/project-structure.md
  • apps/web/package.json
  • apps/web/src/components/AiQuestionDialog.jsx
  • apps/web/src/components/ContentBlock.jsx
  • apps/web/src/components/LessonView.jsx
  • apps/web/src/components/MergeDialog.jsx
  • apps/web/src/components/SectionCard.jsx
  • apps/web/src/lib/collab.js
  • apps/web/src/lib/docxExport.js
  • apps/web/src/lib/docxImport.js
  • apps/web/src/lib/git/sync.js
  • apps/web/src/lib/htmlPreview.js
  • apps/web/src/pages/EditorPage.jsx
  • apps/web/src/pages/HubPage.jsx
  • packages/core/package.json
  • packages/core/src/ageRanges.js
  • packages/core/src/id.js
  • packages/core/src/image.js
  • packages/core/src/jsonExport.js
  • packages/core/src/jsonImport.js
  • packages/core/src/lessonSearch.js
  • packages/core/src/questions.js
  • packages/core/src/spelling.js
  • pnpm-workspace.yaml

📝 Walkthrough

Walkthrough

The change adds @spelling-creator/core with shared lesson utilities, image processing, JSON import/export, and lesson search. The web app imports these modules through package subpaths. Workspace configuration and monorepo documentation describe the new package and runtime boundaries.

Changes

Shared core package migration

Layer / File(s) Summary
Package workspace and runtime configuration
pnpm-workspace.yaml, packages/core/package.json, .oxlintrc.json
The workspace includes packages/*. The core package defines eight subpath exports and its fuse.js dependency. Oxlint rules define Worker and browser runtime overrides.
Shared lesson-domain helpers
packages/core/src/ageRanges.js, packages/core/src/id.js, packages/core/src/questions.js, packages/core/src/spelling.js
The package adds age-range data, ID generation, question metadata and factories, spelling-block helpers, and uppercase-word extraction.
Image, JSON, and lesson search services
packages/core/src/image.js, packages/core/src/jsonExport.js, packages/core/src/jsonImport.js, packages/core/src/lessonSearch.js
The package adds image sizing and WebP conversion, versioned lesson JSON export and normalization-based import, and Fuse.js lesson search.
Web app integration with core exports
apps/web/package.json, apps/web/src/components/*, apps/web/src/lib/*, apps/web/src/pages/*
Web app imports now use @spelling-creator/core subpaths for shared lesson functionality.
Monorepo and deployment documentation
README.md, apps/docs/docs/monorepo/*, apps/docs/docs/web-app/project-structure.md, .github/workflows/deploy.yml
Project documentation describes the core package, runtime-specific modules, web output, and Rspress deployment comments.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant EditorPage
  participant jsonExport
  participant jsonImport
  participant BrowserAPIs
  participant HubPage
  participant lessonSearch
  participant FuseJS
  EditorPage->>jsonExport: exportJson(doc)
  jsonExport->>BrowserAPIs: create JSON blob and trigger download
  EditorPage->>jsonImport: importJsonFile(file)
  jsonImport->>BrowserAPIs: read and parse file
  jsonImport-->>EditorPage: normalized lesson document
  HubPage->>lessonSearch: buildLessonIndex(lessons)
  lessonSearch->>FuseJS: index title and author fields
  HubPage->>lessonSearch: searchLessons(index, query)
  lessonSearch-->>HubPage: ranked lesson results
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.97% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: moving framework-agnostic lesson logic into the new @spelling-creator/core package.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch core-extract-leaves

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

@playforge-coding playforge-coding changed the title core extract leaves Extract framework-agnostic lesson logic into @spelling-creator/core Aug 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
.oxfmtrc.json (1)

13-14: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use .oxfmtrc.jsonc for comments.

Rename the file to .oxfmtrc.jsonc, or remove the comments to keep .oxfmtrc.json as standard JSON.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.oxfmtrc.json around lines 13 - 14, Update the formatter configuration by
renaming .oxfmtrc.json to .oxfmtrc.jsonc so the existing comment and
configuration remain valid; alternatively, remove the comment and retain the
.json filename as strict JSON.

Sources: Coding guidelines, Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/docs/docs/monorepo/overview.md`:
- Around line 13-18: Add an apps/docs row to the monorepo package table,
identifying it as `@spelling-creator/docs` and describing its documentation site
consistently with the existing README and deployment configuration.

In `@apps/docs/docs/web-app/project-structure.md`:
- Line 101: Add the text language tag to the fenced code block at the affected
documentation section, changing the opening fence to specify text while leaving
the directory listing content unchanged.

In `@packages/core/src/jsonImport.js`:
- Around line 62-70: Update normalizeLessonFile to validate the envelope version
alongside parsed.format, rejecting any present version that differs from
LESSON_FILE_VERSION before selecting docLike or normalizing. Preserve acceptance
of bare legacy documents with no format or version, and keep the existing
wrong-format JsonImportError behavior.

---

Nitpick comments:
In @.oxfmtrc.json:
- Around line 13-14: Update the formatter configuration by renaming
.oxfmtrc.json to .oxfmtrc.jsonc so the existing comment and configuration remain
valid; alternatively, remove the comment and retain the .json filename as strict
JSON.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 543fa5e8-d14c-447a-a977-ba97d4f7265f

📥 Commits

Reviewing files that changed from the base of the PR and between 433c288 and f2cd1b5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • .github/workflows/deploy.yml
  • .oxfmtrc.json
  • .oxlintrc.json
  • README.md
  • apps/docs/docs/monorepo/getting-started.md
  • apps/docs/docs/monorepo/overview.md
  • apps/docs/docs/web-app/project-structure.md
  • apps/web/package.json
  • apps/web/src/components/AiQuestionDialog.jsx
  • apps/web/src/components/ContentBlock.jsx
  • apps/web/src/components/LessonView.jsx
  • apps/web/src/components/MergeDialog.jsx
  • apps/web/src/components/SectionCard.jsx
  • apps/web/src/lib/collab.js
  • apps/web/src/lib/docxExport.js
  • apps/web/src/lib/docxImport.js
  • apps/web/src/lib/git/sync.js
  • apps/web/src/lib/htmlPreview.js
  • apps/web/src/pages/EditorPage.jsx
  • apps/web/src/pages/HubPage.jsx
  • packages/core/package.json
  • packages/core/src/ageRanges.js
  • packages/core/src/id.js
  • packages/core/src/image.js
  • packages/core/src/jsonExport.js
  • packages/core/src/jsonImport.js
  • packages/core/src/lessonSearch.js
  • packages/core/src/questions.js
  • packages/core/src/spelling.js
  • pnpm-workspace.yaml

Comment thread apps/docs/docs/monorepo/overview.md
Comment thread apps/docs/docs/web-app/project-structure.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

🧹 Nitpick comments (1)
.oxfmtrc.json (1)

13-14: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use .oxfmtrc.jsonc for comments.

Rename the file to .oxfmtrc.jsonc, or remove the comments to keep .oxfmtrc.json as standard JSON.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.oxfmtrc.json around lines 13 - 14, Update the formatter configuration by
renaming .oxfmtrc.json to .oxfmtrc.jsonc so the existing comment and
configuration remain valid; alternatively, remove the comment and retain the
.json filename as strict JSON.

Sources: Coding guidelines, Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/docs/docs/monorepo/overview.md`:
- Around line 13-18: Add an apps/docs row to the monorepo package table,
identifying it as `@spelling-creator/docs` and describing its documentation site
consistently with the existing README and deployment configuration.

In `@apps/docs/docs/web-app/project-structure.md`:
- Line 101: Add the text language tag to the fenced code block at the affected
documentation section, changing the opening fence to specify text while leaving
the directory listing content unchanged.

In `@packages/core/src/jsonImport.js`:
- Around line 62-70: Update normalizeLessonFile to validate the envelope version
alongside parsed.format, rejecting any present version that differs from
LESSON_FILE_VERSION before selecting docLike or normalizing. Preserve acceptance
of bare legacy documents with no format or version, and keep the existing
wrong-format JsonImportError behavior.

---

Nitpick comments:
In @.oxfmtrc.json:
- Around line 13-14: Update the formatter configuration by renaming
.oxfmtrc.json to .oxfmtrc.jsonc so the existing comment and configuration remain
valid; alternatively, remove the comment and retain the .json filename as strict
JSON.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 543fa5e8-d14c-447a-a977-ba97d4f7265f

📥 Commits

Reviewing files that changed from the base of the PR and between 433c288 and f2cd1b5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • .github/workflows/deploy.yml
  • .oxfmtrc.json
  • .oxlintrc.json
  • README.md
  • apps/docs/docs/monorepo/getting-started.md
  • apps/docs/docs/monorepo/overview.md
  • apps/docs/docs/web-app/project-structure.md
  • apps/web/package.json
  • apps/web/src/components/AiQuestionDialog.jsx
  • apps/web/src/components/ContentBlock.jsx
  • apps/web/src/components/LessonView.jsx
  • apps/web/src/components/MergeDialog.jsx
  • apps/web/src/components/SectionCard.jsx
  • apps/web/src/lib/collab.js
  • apps/web/src/lib/docxExport.js
  • apps/web/src/lib/docxImport.js
  • apps/web/src/lib/git/sync.js
  • apps/web/src/lib/htmlPreview.js
  • apps/web/src/pages/EditorPage.jsx
  • apps/web/src/pages/HubPage.jsx
  • packages/core/package.json
  • packages/core/src/ageRanges.js
  • packages/core/src/id.js
  • packages/core/src/image.js
  • packages/core/src/jsonExport.js
  • packages/core/src/jsonImport.js
  • packages/core/src/lessonSearch.js
  • packages/core/src/questions.js
  • packages/core/src/spelling.js
  • pnpm-workspace.yaml
🛑 Comments failed to post (1)
packages/core/src/jsonImport.js (1)

62-70: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject unsupported lesson-file versions.

normalizeLessonFile validates format but ignores version. A future incompatible envelope can pass normalization, lose unsupported fields, and then overwrite the source when the user saves it.

Reject envelope versions other than LESSON_FILE_VERSION. Continue to accept bare legacy documents that have no format.

Proposed fix
-import { LESSON_FILE_FORMAT } from "./jsonExport.js";
+import {
+  LESSON_FILE_FORMAT,
+  LESSON_FILE_VERSION,
+} from "./jsonExport.js";
...
   if (parsed.format && parsed.format !== LESSON_FILE_FORMAT) {
     throw new JsonImportError(
       `This JSON file is “${parsed.format}”, not a Spelling Creator lesson.`,
     );
   }
+  if (
+    parsed.format === LESSON_FILE_FORMAT &&
+    parsed.version !== LESSON_FILE_VERSION
+  ) {
+    throw new JsonImportError(
+      `This lesson file uses unsupported version “${parsed.version}”.`,
+    );
+  }
📝 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.

  // A wrong but explicit format marker is a clear, actionable error.
  if (parsed.format && parsed.format !== LESSON_FILE_FORMAT) {
    throw new JsonImportError(
      `This JSON file is “${parsed.format}”, not a Spelling Creator lesson.`,
    );
  }
  if (
    parsed.format === LESSON_FILE_FORMAT &&
    parsed.version !== LESSON_FILE_VERSION
  ) {
    throw new JsonImportError(
      `This lesson file uses unsupported version “${parsed.version}”.`,
    );
  }
  const docLike =
    parsed.doc && typeof parsed.doc === "object" ? parsed.doc : parsed;
  return normalizeDoc(docLike);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/jsonImport.js` around lines 62 - 70, Update
normalizeLessonFile to validate the envelope version alongside parsed.format,
rejecting any present version that differs from LESSON_FILE_VERSION before
selecting docLike or normalizing. Preserve acceptance of bare legacy documents
with no format or version, and keep the existing wrong-format JsonImportError
behavior.

The lesson model is currently spread across apps/web/src/lib, which means the
Worker and the MCP server cannot reuse any of it. This adds packages/core and
moves the first tier out: the modules with no React dependency and no imports
back into the app.

All eight moves are pure renames — no content changed, so the diff is the
import rewrites and the package wiring.

The package has no build step and no barrel entry, matching the convention
@spelling-creator/mcp already uses: every module is its own subpath export.
That keeps browser-only modules out of the Worker's import graph, so importing
/questions can never drag in something that touches document.

Two of the moved modules (image, jsonExport) do still reach for the DOM, and
only the web app imports them today. Rather than grant core the browser env
wholesale, it is linted against the narrower worker env and those two files
are opted back into browser by name — so anything added later that reaches for
a browser global fails lint instead of breaking inside the Worker. They move
behind a /browser subpath when the rest of that tier is extracted.

fuse.js moves with lessonSearch to core's dependencies.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@playforge-coding
playforge-coding merged commit cde68dc into master Aug 5, 2026
1 of 2 checks passed
@playforge-coding
playforge-coding deleted the core-extract-leaves branch August 5, 2026 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant