Skip to content

Flutter IB JSON API generator - #802

Merged
JatsuAkaYashvant merged 9 commits into
CircuitVerse:masterfrom
SantamRC:flutter-api
Sep 2, 2026
Merged

JatsuAkaYashvant merged 9 commits into
CircuitVerse:masterfrom
SantamRC:flutter-api

Conversation

@SantamRC

@SantamRC SantamRC commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Replaces the markdown page API with a structured JSON API generated from the
Jekyll book, for the Interactive Book Flutter app.

The book is authored once in docs/ as kramdown markdown and served two ways:
Jekyll renders the website, and utils/md2json renders the same source into a
flat view model the mobile client renders natively.

python3 utils/md2json

No flags, no arguments. Reads docs/, rewrites the whole output tree, prints
warnings for anything it cannot translate. No third-party dependencies.

Important

Breaking: https://learn.circuitverse.org/_api/ is no longer published.
Consumers move to https://learn.circuitverse.org/api/. Nothing in this
repository referenced _api, and the Flutter app — its only known consumer —
now uses the structured form. Worth a maintainer confirming no external
consumer remains.

Why replace rather than add

The two APIs answer the same question in different shapes:

_api/ (removed) api/ (added)
Page content Jekyll-rendered HTML string structured views array
Includes expanded to raw simulator HTML/JS sub_type naming a native widget
Citations resolved into HTML by jekyll-scholar [n] markers plus a reference list
Lists, tables, quizzes HTML <ol>, <table> typed widget payloads
Requires jekyll-admin plus a running server nothing; reads docs/ directly

Serving both would mean maintaining two representations of every page. The HTML
form suits a WebView; the app renders natively, so it needs the structured form.

Removing _api also simplifies the deploy. Generating it meant booting a
detached Jekyll server, crawling it over HTTP with an unqualified sudo python,
then pkill-ing the server. The workflow is now build, generate, deploy.
jekyll-admin stays in the Gemfile: it is in the :jekyll_plugins group and
still powers the local /admin editing UI.

The API

Published to GitHub Pages by the existing deploy job, alongside the site.

GET {base}/api/navbar.json                    chapters, each with its path
GET {base}/api/{path}/0.json                  chapter index
GET {base}/api/{path}/{sub-chapter id}.json   one section
GET {base}/api/about.json
GET {base}/api/guidelines.json

57 documents: 10 chapter indexes, 44 sections, navbar.json, about.json,
guidelines.json. Each page is {"name": str, "views": [...]} where views is
a flat list a single ListView.builder can walk. Markdown nesting is
collapsed deliberately: a tree would only have to be flattened again on device.

{"name": "Registers",
 "views": [
   {"type": "widget", "sub_type": "toc", "items": ["Introduction", "..."]},
   {"type": "text", "size": "H1", "content": "Introduction", "scrollToId": 0},
   {"type": "text", "size": "H3", "content": "A flip-flop is a 1 bit memory cell..."},
   {"type": "widget", "sub_type": "table", "content": {"heading": [], "rows": []}}
 ]}

H1 is a section heading, H2 a sub-heading, H3 body copy. Headings carry a
scrollToId matching their index in the toc widget, which drives
tap-to-scroll. navbar.json carries each chapter's directory so a client can
build URLs without knowing the chapter ordering in advance.

Widgets: toc, chapter_contents, table, bullet_list, numbered_list,
clipboard, image, pop-quiz, and ten interactive simulators mapped from
_includes/.

How it works

Module Responsibility
config.py paths, widget tables, heading rules
frontmatter.py Jekyll front matter
inline.py inline markdown/HTML to plain text
blocks.py line scanner: markdown blocks to view documents
bibliography.py BibTeX parsing and IEEE citation rendering
model.py Page / Section / Chapter
book.py chapter discovery, ordering, navbar
output.py serialisation and writing
cli.py the run loop

blocks.py is a single-pass line scanner rather than an AST parser: the output
is flat, and the input is a closed corpus in a known house style. Chapter and
section ordering comes from the same nav_order front matter Jekyll uses, so
the app's navigation matches the website's without a second source of truth.

The output tree is generator-owned. Each run builds into a staging directory and
swaps it in only on success, so a removed or renumbered section cannot leave
stale JSON and a failure part-way through cannot leave a partial tree. The
previous tree is moved aside rather than deleted and restored if the swap fails.
The generator refuses to write to any path overlapping docs/ or
_bibliography/.

Content recovered

Three classes of Jekyll construct were being dropped. The generator now runs
with zero warnings.

Embedded simulators. An <iframe> following prose without a blank line was
absorbed into the paragraph and then stripped as an HTML tag. Twelve embedded
CircuitVerse simulators were missing from the output; they are now emitted.

Citations. {% cite %} / {% bibliography %} are rendered on the website by
jekyll-scholar (style: ieee-with-url). Unresolved, they left dangling prose —
docs/binary-algebra/shannon.md read as "...can be found in Section 1.9 in
and in Section 3.2 in ."
All 13 entries in _bibliography/ are now resolved and
rendered IEEE style, reusing the existing numbered_list widget.

Interactive includes. binary2.html, flipflop2.html and
application1.html were silently dropped and are now mapped to widgets.

Chapter contents. logic-design files its {% include chapter_toc.html %}
under ## Table of contents while the other nine use ## Chapter contents, so
its index page came out empty. Handled in the generator; docs/ is unchanged.

The one docs/ change

docs/logic-design/kmaps.md mixed tab and 8-space indentation inside one quiz
list. A tab expands to 4 columns and the spaces to 8, so three options parsed as
shallower than their siblings and were lifted to the top level. The quiz
rendered as a question with no correct answer plus a phantom question titled
"3-variable", and lost an option from an earlier question.

Two lines, whitespace only, no wording changed. kramdown resolves indentation
the same way, so this should correct the website rendering too. Happy to split
it into its own PR if preferred.

Testing

Against the current docs/: 57 documents, 10 chapters, 44 sections, 21 quizzes
(70 questions), 159 images, 60 tables, 69 code blocks, 0 warnings.

  • every URL derivable from navbar.json resolves to valid JSON, with no
    orphaned documents
  • fault injection on the output swap: a failure during generation leaves the
    tree untouched, a failure during the swap restores it, and a failure of both
    leaves a named backup rather than deleting it
  • the path guard refuses docs/, docs/api, _bibliography/, the repository
    root, / and out/../docs
  • deploy order simulated: with out/ populated by a build, the run adds
    out/api/ and leaves the rest of the site untouched

Notes for reviewers

  • The generated tree is gitignored; this PR adds the generator, not its output.
  • about.json and guidelines.json come from about.md and CONTRIBUTING.md
    at the repository root, which Jekyll already builds as ordinary pages.
  • Reference entries are plain text, so URLs are not tappable links. A dedicated
    references widget carrying {text, url} would fix that if wanted.
  • docs/logic-design/kmaps.md marks "Entries" as the answer to "___ are known
    as diagonal mapping?", which looks wrong on the merits. Left alone: that is a
    content decision, not a parsing one.

Copilot AI lite review requested due to automatic review settings August 28, 2026 09:16
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Added the utils.md2json package for converting Markdown documentation into JSON. The package parses front matter, inline Markdown, blocks, lists, quizzes, Liquid tags, iframes, and bibliography citations. It discovers chapters and sections, builds pages and navigation data, writes staged JSON output, and provides package and direct-script CLI entry points. The deployment workflow runs the generator after the Jekyll build.

Merge Risk: 🟡 Moderate · up to 151da

The generator can produce incorrect mobile-book content, publish an incomplete API, and—with an overlapping configuration—replace source documentation with generated files. These bounded but concrete correctness and publication risks require fixes or explicit owner acceptance before merge.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 45 functions across 11 files. (2 skipped: …
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 identifies the main change: adding a Flutter Interactive Book JSON API generator.
Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 45 functions across 11 files. (2 skipped: 2 unsupported.)

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copilot AI 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.

Pull request overview

Adds a new Python-based utils/md2json package that converts the repository’s Jekyll markdown book (docs/) into the view-JSON format consumed by the app, including support for includes/widgets and BibTeX-backed citations.

Changes:

  • Introduces a markdown-to-view-JSON parser (blocks.py + inline.py) and book discovery/navigation builder (book.py).
  • Adds BibTeX parsing + {% cite %} / {% bibliography %} handling to preserve citation markers and generate References content.
  • Adds a CLI entrypoint that regenerates an output tree in one run (cli.py, __main__.py, output.py).

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
utils/md2json/init.py Exposes public module API and documents the package purpose.
utils/md2json/main.py Supports running the generator as a module or by executing the package directory.
utils/md2json/bibliography.py Parses BibTeX and renders citations/references into plain text for widgets.
utils/md2json/blocks.py Block-level markdown parser that builds the view-document list (widgets/text/TOC).
utils/md2json/book.py Discovers chapters/sections from docs/ and assembles navbar + pages.
utils/md2json/cli.py End-to-end regeneration loop writing navbar + per-page JSON outputs.
utils/md2json/config.py Central configuration for paths, widget mappings, and dropped sections/headings.
utils/md2json/frontmatter.py Extracts flat Jekyll front matter and returns the remaining markdown body.
utils/md2json/inline.py Flattens inline markdown/HTML to plain text expected by widgets.
utils/md2json/model.py Dataclasses for the parsed book/page structure.
utils/md2json/output.py JSON serialization + write-to-disk helper.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread utils/md2json/config.py
Comment thread utils/md2json/output.py Outdated
Comment thread utils/md2json/bibliography.py
Comment thread utils/md2json/bibliography.py Outdated

@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: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cd67a57c-d40f-4edc-989b-28f21f5ada8c

📥 Commits

Reviewing files that changed from the base of the PR and between 0afa9b9 and 8515f5d.

📒 Files selected for processing (11)
  • utils/md2json/__init__.py
  • utils/md2json/__main__.py
  • utils/md2json/bibliography.py
  • utils/md2json/blocks.py
  • utils/md2json/book.py
  • utils/md2json/cli.py
  • utils/md2json/config.py
  • utils/md2json/frontmatter.py
  • utils/md2json/inline.py
  • utils/md2json/model.py
  • utils/md2json/output.py

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.

Comment thread utils/md2json/bibliography.py
Comment thread utils/md2json/cli.py
The generator's widget vocabulary was split across two files: INCLUDE_WIDGETS
lived in config.py while nine sub_type strings were literals in blocks.py, so
"what does the app render?" could not be answered from one place and renaming a
widget meant grepping the parser.

- move the nine generator-named sub_types into config.py as WIDGET_* constants
- name image.html / chapter_toc.html once in config; both were duplicated as
  literals in blocks.py, with chapter_toc.html already in STRUCTURAL_INCLUDES
- move the "references" heading into config alongside the other heading rules
- locate the repo by walking up to _config.yml instead of parents[2], which
  silently resolved to the wrong tree if the package were moved

Also fixes a quiz in docs/logic-design/kmaps.md that mixed tab and 8-space
indentation in one list. A tab expands to 4 columns and the spaces to 8, so
three options parsed as shallower than their siblings and were lifted to the
top level, producing a question with no correct answer plus a phantom question
titled "3-variable", and dropping an option from an earlier question. Whitespace
only, no wording changed; kramdown resolves indentation the same way, so this
should correct the website rendering too.

Generated output is byte-for-byte unchanged, verified against a snapshot.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
utils/md2json/blocks.py (1)

184-193: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Stop paragraph collection before an iframe.

When handle_paragraph() encounters an iframe line, it appends the line to the paragraph instead of returning control to parse(). The iframe then does not reach handle_iframe(), so no WIDGET_IMAGE is emitted. Add the same iframe detection used by parse() to the paragraph terminators.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 967eddba-f0ef-4203-b317-d8ff92bd8da3

📥 Commits

Reviewing files that changed from the base of the PR and between 8515f5d and 1c24307.

📒 Files selected for processing (5)
  • .gitignore
  • docs/logic-design/kmaps.md
  • utils/md2json/blocks.py
  • utils/md2json/cli.py
  • utils/md2json/config.py

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.

Comment thread utils/md2json/blocks.py Outdated
Comment thread utils/md2json/blocks.py
Comment thread utils/md2json/config.py Outdated
Recovers content the parser was dropping, and makes the output tree safe to
regenerate.

handle_paragraph() did not treat an iframe as a paragraph terminator, so an
iframe that followed prose without a blank line was absorbed into the paragraph
and then stripped as an HTML tag. Twelve embedded CircuitVerse simulators were
missing from the generated JSON as a result; they are now emitted.

collect_list() accepted ordered and unordered markers at the same root level,
so a bullet list followed by a numbered list merged into one widget typed from
the first item. Collection now stops when a root-level marker type changes.
Nested items still mix markers freely, which is how {:.quiz} encodes answers.

CitationRegistry numbered keys it could not resolve but rendered() omitted them,
so an inline [2] could point at the first entry in the list. Unresolved keys now
render a visible placeholder, keeping markers and list positions aligned.

The output tree is generator-owned: it is now built in a staging directory and
swapped in only after a successful run, so a removed or renumbered section
cannot leave stale JSON and a failure part-way through cannot leave a partial
tree.

Also:
- _find_repo_root() fell back to parents[2], one level above the repository,
  since it is passed utils/md2json rather than the config file itself
- write_document() raised ValueError labelling any path outside OUTPUT_PATH
- dropped the "1. TOC" placeholder guard, which never fired on any page because
  the dropped-section handling already removes it, and could only misfire on a
  legitimate single-item list
- fixed a docstring opening with four quote characters
- documented the remaining undocumented functions (45/45)

Verified against a pre-change snapshot: the only content differences are the
twelve recovered iframe widgets.

@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: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2bc97d75-c831-404f-b2b6-e6c3c54e2f02

📥 Commits

Reviewing files that changed from the base of the PR and between 1c24307 and a4aa66e.

📒 Files selected for processing (6)
  • utils/md2json/bibliography.py
  • utils/md2json/blocks.py
  • utils/md2json/book.py
  • utils/md2json/cli.py
  • utils/md2json/config.py
  • utils/md2json/output.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • utils/md2json/book.py
  • utils/md2json/blocks.py

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.

Comment thread utils/md2json/cli.py Outdated
The previous swap deleted the existing output before moving the staging tree
into place, so a failure in that move left no output at all, and concurrent
readers could observe the path as absent between the two operations.

Move the existing tree to a sibling backup instead of deleting it, then swap
the staging tree in, then discard the backup. If the swap fails the backup is
restored. If both the swap and the restore fail the backup is kept rather than
cleaned up, since it then holds the only copy, and its path is reported.

Verified by fault injection: a failure during generation leaves the tree
untouched, a failure during the swap restores all 57 files, and a failure of
both leaves a named backup behind. No staging or backup directories are left
in any case.
The generator wrote to a directory that never reached the deploy, so the JSON
was only ever available locally. The existing page API is published by building
the site into out/ and having utils/api_generator.py write out/_api before
peaceiris/actions-gh-pages deploys out/ to GitHub Pages. Serve the book API the
same way.

- write to out/api/ instead of a top-level directory, so the deploy publishes it
- run the generator in the deploy workflow, after the jekyll build, since jekyll
  clears its destination directory
- refuse to generate into the repository root or any ancestor of docs/, as the
  output tree is now inside the build output and is replaced wholesale

Endpoints become <site>/api/navbar.json, <site>/api/about.json and
<site>/api/<chapter>/<n>.json, alongside the existing <site>/_api/pages/.

Verified in deploy order: with out/ already populated by a build, the run adds
out/api/ with all 57 documents, leaves the rest of the site untouched, and
leaves no staging or backup directories that would be published.
coderabbitai[bot]
coderabbitai Bot previously requested changes Aug 29, 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: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 500d9092-2153-4477-98c9-32398af1250d

📥 Commits

Reviewing files that changed from the base of the PR and between 9dd9a2c and 151da84.

📒 Files selected for processing (4)
  • .github/workflows/deploy.yml
  • .gitignore
  • utils/md2json/cli.py
  • utils/md2json/config.py
💤 Files with no reviewable changes (1)
  • .gitignore

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.

Comment thread utils/md2json/cli.py Outdated
The site published two APIs. utils/api_generator.py crawled jekyll-admin's
endpoint to mirror out/_api, serving each page as Jekyll-rendered HTML inside a
JSON envelope. utils/md2json now publishes out/api, serving the same pages as a
structured view model. The app consumes the structured form, so the markdown
API is retired.

Removing it also simplifies the deploy. Generating _api required booting a
detached jekyll server, crawling it over HTTP with an unqualified `sudo python`,
then pkill-ing the server: three steps, a background process and a second
interpreter invocation. The generator reads docs/ directly, so the workflow
drops to build, generate, deploy.

jekyll-admin stays in the Gemfile: it is in the :jekyll_plugins group and still
powers the local /admin editing UI for contributors.

BREAKING CHANGE: https://learn.circuitverse.org/_api/ is no longer published.
Consumers should move to https://learn.circuitverse.org/api/, which serves
navbar.json, about.json, guidelines.json and <chapter>/<n>.json.
navbar.json identified each chapter by id and display name only, while its
documents are generated into a slug directory. As a local bundle a client could
carry its own ordered list of directories; as a remote API there was no way to
build a URL from the navbar at all, and any change to a chapter's nav_order
would silently repoint a hardcoded list at the wrong chapter.

Each chapter now carries the directory it was generated into, so a client can
request "<base>/<path>/0.json" for a chapter index and "<base>/<path>/<id>.json"
for a section. The field is additive; existing consumers ignore it.

Verified that every URL derivable from navbar.json resolves to valid JSON, and
that no generated document is unreachable from it.
The guard rejected only paths strictly above docs/, so OUTPUT_PATH == DOCS_PATH
passed it. The swap would then move docs/ aside, write the generated JSON in its
place, and delete the backup once the swap succeeded, destroying the book
source. A path inside docs/ was likewise allowed and would have replaced a
chapter directory.

Reject overlap in either direction, equal to a source tree, containing it, or
inside it, and resolve the paths first so a relative path or symlink cannot slip
past. _bibliography/ is protected alongside docs/, since it carries the same
risk. The repository root is covered by the containment check.

Verified: out/api generates normally, while docs/, docs/api, docs/logic-design,
_bibliography/, the repository root, / and out/../docs are all refused.
The workflow file is stored with CRLF. Editing it rewrote the whole file with
LF, so the diff showed all 34 lines as changed rather than just the three
removed steps. No content change.
@JatsuAkaYashvant
JatsuAkaYashvant dismissed coderabbitai[bot]’s stale review September 2, 2026 17:53

Dismissing as stale, all flagged issues were fixed in later commits (a4aa66e, aecb0f1). No CodeRabbit review since aecb0f1; the only commit after it (834cfe7) is CRLF-only, no functional change.

@JatsuAkaYashvant
JatsuAkaYashvant merged commit 38767e9 into CircuitVerse:master Sep 2, 2026
3 checks passed
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.

3 participants