Skip to content

Rewrite the docs - #1052

Draft
Archmonger wants to merge 131 commits into
mainfrom
new-docs
Draft

Archmonger wants to merge 131 commits into
mainfrom
new-docs

Conversation

Comment thread .github/workflows/.hatch-run.yml
Replace the empty ReactPy v1 placeholders with pages written from the
2.0.0b13 source, each verified by execution and, where the claim is
about the browser, in headless Chrome over CDP.

- creating-html-tags: html invents tags via __getattr__ (no allow-list),
  the underscore rules, exact-dict attribute discrimination, the void
  sets, script's custom constructor, svg's broader void set.
- creating-vdom-event-handlers: the three buckets keyed on callable not
  name, plain-callable vs EventHandler argument shapes, and the limits
  of bytecode option detection -- bound methods are not inspected.
- vdom-mutations: a model belongs to its render; module-level elements
  are shared across connections, so one user's mutation leaks into
  every other page (proven with two tabs, two sockets).
- convert-between-vdom-and-html: reactpy_to_string lowercases attribute
  names and drops handlers; string_to_reactpy renames to React props,
  intercepts links, and infers keys from ids.
- communicate-data-between-server-and-client: only two channels exist;
  a custom socket message is dropped as "without handler". File inputs
  send metadata, never bytes -- read the file in the page and submit it
  through a named field. A setter called off-loop fails silently.
- creating-backends: the four jobs, serve_layout plumbing, and the
  websocket.connect event that otherwise kills the socket on connect.
- manually-register-a-client: mountReactPy by hand, two mounts in one
  node leaves an unclosable zombie socket, and registering JavaScript
  components through reactpy.reactjs.

Examples live in docs/examples/<page_slug>/ and are included via
include-markdown. hatch fmt --check, pyright, and mkdocs build --strict
all pass.
Replace the ReactJS placeholder with a page written from the 2.0.0b13
source, with every behavioural claim checked by execution against a real
Layout rather than read off the docstrings.

- dependencies has four meanings, and the default is inference from the
  closure's free variables (_try_to_infer_closure_values), not React's
  "run after every render"; measured all four settings side by side.
- dependencies are compared with strictly_equal, not by id as the
  docstrings claim: a fresh equal list does not re-trigger, and neither
  does a structurally identical local def, so such an effect keeps its
  first closure and reads stale state forever.
- registering an effect above the state it reads raises ValueError: Cell
  is empty during dependency inference, and renders a blank region.
- cleanup measured both ways: 4 renders -> 4 live subscribers with one
  publish firing 4 callbacks without a clean-up, 1 with one, 0 after
  unmount.
- two effects in a component run in declaration order; across components
  children run first and siblings in reverse.
- an async effect is cancelled at its await, so the superseded fetch
  never reaches its setter -- the data source logged only the new answer.
- no Strict Mode and no double-invoke, so restart-safety is the reader's
  own test to write.

Examples live in docs/examples/synchronizing_with_effects/ and are
included via include-markdown. hatch fmt --check, pyright,
reactpy-flake8 hook lint, and mkdocs build --strict all pass.
docs/requirements.txt pins no mkdocstrings version, so CI installs
mkdocstrings-python 2.x, where the handler option is `inventories` and
the old `import` key no longer exists. On the committed config the
build dies during plugin configuration:

    TypeError: PythonConfig.__init__() got an unexpected keyword
    argument 'import'

`mkdocs build --strict` therefore fails for every push to new-docs,
not just for changed pages. Renamed to `inventories`; verified with the
minify and git-revision-date plugins enabled, i.e. with the same
configuration CI uses.
Replace the ReactJS placeholder with a page written from the 2.0.0b13
source. Every cost claim is a measured render count, not an appeal to
React's reasoning.

- storing a derived value plus an Effect: 6 renders for two keystrokes
  and a wrong first paint ("(nothing yet)"); deriving it: 3 renders.
  The stale first render is a real patch the client receives.
- use_memo: 3 renders, filter ran once across two keystrokes; re-ran
  only on the filter change. It caches values, never renders.
- resetting on prop change via key: 1 extra render, whole subtree fresh.
- React's render-phase setState does NOT work here: measured 4 renders
  and a delivered patch carrying items=[a,b,c,i4] with selected=a -- the
  stale frame ships. Taught the derived read instead (3 renders).
- an effect setting state it depends on looped 86 times; ReactPy has no
  detector. Hooks inside an effect body raise "No life cycle hook is
  active" and only ever reach the server log.
- transform_data caught a real bug while verifying: storing the response
  alone showed the previous report's rows under the new heading. The
  example now stores the response with the name that produced it.

Examples live in docs/examples/you_might_not_need_an_effect/. hatch fmt
--check, pyright, reactpy-flake8 hook lint, and mkdocs build --strict
all pass.
Replace the ReactJS port with content verified against reactpy 2.0.0b13 by
running each example in a real Layout.

The ReactJS page describes a lifecycle ReactPy does not implement, so the
page teaches what actually happens and flags the traps found by measurement:

- dependencies are compared with strictly_equal, not by id: a locally defined
  effect function compares code-wise and never counts as changed, so the Effect
  silently freezes on its first closure (frozen_body)
- reactpy-flake8 does not check named or decorated effect bodies, nor use_memo
  or use_callback, so the stale-value bug is unverified by any tool -- the page
  replaces "React verifies your dependencies" with the manual check plus a
  Layout-based test recipe
- a raising Effect is silent in the page, the browser console and the server
  log; the sole ERROR record appears only at unmount, where the tasks are
  awaited (silent_failure)
- async cancellation lands at the next await, so runs overlap and a superseded
  run can still write state (overlapping_runs)
- a blocking sync effect delays the next event on that connection by its own
  duration (blocking_effect)
- effect ordering is children-first, reverse siblings, declaration order within
  a component; only the last is a contract (ordering)

Examples: setup_cleanup, reactive_values, frozen_body, overlapping_runs,
gating, silent_failure, ordering, blocking_effect.

Verified: hatch fmt --check, pyright, reactpy_flake8 hook lint, and
mkdocs build --strict all pass.
Replace the ReactJS port with content verified against reactpy 2.0.0b13 by
driving each example in a real Layout.

React's page is built around Effect Events, which ReactPy does not have
(verified absent, along with useOptimistic and useTransition). The page keeps
the intent-vs-effect lesson and replaces the rest with what was measured:

- an effect driven by click-only state runs once, not per click, because the
  trigger flag stops changing (intent_in_effect)
- ReactPy stores one handler per target and resolves it at dispatch time, so a
  normal click already reads current values -- the stale-closure class of bug
  narrows to events delivered before the next render (handler_freshness)
- measured: two back-to-back naive clicks produce one item where the functional
  updater produces two
- async handlers may set state after an await; after unmount the write is
  dropped with only a debug log line
- use_callback pins identity, not freshness, and identity buys nothing for
  render cost: there is no memo and no bailout, so an unchanged child re-ran its
  body on all 4 renders (callback_identity)
- a callback whose dependency is a locally defined function compares code-equal
  and freezes on its first closure while the counter keeps ticking
  (callback_frozen_dep)

Examples: intent_in_effect, intent_in_handler, handler_freshness,
callback_identity, callback_frozen_dep, latest_value_ref.

Verified: hatch fmt --check, pyright, reactpy_flake8 hook lint, and
mkdocs build --strict all pass.
Replace the ReactJS port with content verified against reactpy 2.0.0b13 by
driving each example in a real Layout.

The ReactJS page assumes a compiler that checks the dependency list and treats
every rebuilt object as a new dependency. Neither holds in ReactPy, so the page
teaches what was measured:

- dependencies go through strictly_equal: a type gate, then co_* comparison for
  local functions, then `==`. Measured over 5 renders: a rebuilt dict/list runs
  the Effect once, a dataclass/NamedTuple runs on real content change only, and
  a plain class instance with no __eq__ runs on all 5. React's "rebuilt object
  loops forever" warning therefore does not apply -- and must not be relied on.
- strictly_equal(1, True) is False despite 1 == True, and a tuple swapped for a
  list counts as changed, because of the type gate
- reading state only to compute the next state is not a dependency; listing it
  instead never settles -- measured 400 renders with no settle and 800 body runs
- the interval A/B: delay in the list restarts the loop 4 times where the
  ref-held delay restarts it 2, while still ticking with the newest delay
- a missing dependency is silent, so unread_dep renders the stale summary next
  to the live values (count=0/theme=light against count=2/theme=dark)

Examples: unread_dep, unrelated_effects, next_state_updater,
restarting_interval, stable_interval, hoisted_constant, equality_deps.

Verified: hatch fmt --check, pyright, reactpy_flake8 hook lint, and
mkdocs build --strict all pass.
…pages

Two defects in the examples I added on this branch made the rendered snippets
wrong:

- 19 files put the `# start` marker inside the module docstring, so
  `include ... start="# start"` began mid-docstring and every code block on the
  page opened with an orphan `"""`. The marker now sits above the first
  statement that is neither the docstring nor an import, matching
  docs/examples/conditional_rendering -- so imports stay hidden while
  lesson-critical module constants and helpers stay visible.

- 40 files carried a `# end` marker that no page passed `end=` to honour, so
  the comment and, in several cases, the harness-only `app()` root were rendered
  into the snippet. 150 of the 220 pre-existing `# start` examples have no
  `# end` at all; the stragglers are removed to match, rather than adding
  `end=` and cropping away the `app()` that carries the buttons the page tells
  you to press.

Purely marker placement: the diff changes no code other than those comment
lines. Verified by AST audit (no snippet uses a name defined only above its
marker, except latest_state, which follows the repo's own convention),
hatch fmt --check, pyright, and mkdocs build --strict -- all five affected
pages now render zero leaked markers, and all six behaviour suites still pass.
Replace the ReactJS port with content verified against reactpy 2.0.0b13 by
driving each example in a real Layout.

React's page leans on the compiler that enforces the Rules of Hooks; ReactPy
has only reactpy-flake8, so the page documents exactly what that tool sees,
measured with reactpy_flake8.run.run_checks against files differing only in the
function name:

- the hook-detection regex is ^_*use_\w+$ (reactpy_flake8/run.py), so use_form
  and _use_form get the accurate REACTPY102 while useForm and get_form get
  REACTPY101 at every inner use_state -- i.e. snake_case naming buys the good
  diagnostic
- REACTPY102 does fire on a conditional hook inside a recognised hook
  (hook_order_violation is intentionally flagged, matching the existing
  reference_rules_of_hooks/conditional_hook example)
- nothing checks hook counts at runtime: flipping the branch shifts the state
  slots and bio silently becomes nickname, with zero WARNING/ERROR log records
- a callback passed into a hook and listed as a dependency never counts as
  changed (strictly_equal compares co_* attributes), so the poll keeps calling
  the first render's handler; the ref-refreshed-during-render variant tracks
  (callback_dep vs handler_argument)
- calling the same hook twice gives independent state (isolated_state)

Examples: extract_hook, isolated_state, hook_order_violation, hook_order_safe,
callback_dep, handler_argument.

Verified: hatch fmt --check, pyright, and mkdocs build --strict pass; the page
renders with no leaked include markers and no broken relative links.
…tch pages

An accuracy audit against reactpy 2.0.0b13 found claims in the already-authored
escape-hatch pages that execution contradicts. Each fix was reproduced before
editing:

- convert-between-vdom-and-html: `string_to_reactpy('<img src=x onerror=...>')`
  puts the payload in `inlineJavaScript`, NOT `attributes` -- and the client
  compiles inlineJavaScript into an event-handler prop whose body is `eval()`,
  so the warning understated an XSS. Also: `component_to_vdom` does recurse for
  component children (verified two levels down), so "one level deep" was wrong
  and contradicted the adjacent "recursively".
- vdom-mutations: the snippet crashed as written. `html.div("x")` has no
  "attributes" key, so `d["attributes"]["id"]` raises KeyError before any
  guard; and `d["ref"] = my_ref` raises NameError because Python evaluates the
  right-hand side first. Snippet now runs.
- creating-vdom-event-handlers: "one object, one target id, reused" is false --
  one @event handler on two buttons produced /0/0:onClick and /0/1:onClick.
  Forcing a shared target instead breaks unmount (KeyError from
  Layout._unmount_model_states). The debounce detector stops at the FIRST
  assignment it settles, so two assignments are order-dependent (200 vs None),
  not "give up on all of them". `merge_event_handlers` has no caller anywhere
  in the installed package.
- creating-html-tags: `_flatten_children` recurses, it is not one level. The
  client uses `createElementNS` with a per-tag namespace switch, not
  `createElement`; the viewBox advice stands, now with the right reason.
- creating-backends: the stock `send_json` sends a BINARY frame, which is why
  ReactPyWebsocket.send_json overrides it to send text -- the client never sets
  binaryType, so a Blob reaches `JSON.parse` and throws (verified in headless
  Chrome). The previous text said both work.
- communicate-data-between-server-and-client: the `use_effect` + `async def`
  TypeError is raised during render and yields a blank region; it is never
  logged as "Error in effect", which is the signature of a raising effect body.
- manually-register-a-client: under `ReactPy` the componentPath must be
  omitted, not "/"; a "/" yields /reactpy// and a NotImplementedError from
  match_dispatch_path. Also fixed an anchor pointing at a non-existent heading.

Verified: hatch fmt --check and mkdocs build --strict pass, mkdocs reports no
unresolved anchors anywhere in docs/src, all 24 example files under these pages
compile, and every quoted object literal matches live interpreter output.
These pages were authored in 88b7d45 but kept their 🚧 nav marker, so readers
saw "VDOM Mutations 🚧" in the sidebar and in the page's own <h1> -- which
mkdocs-material derives from the nav title -- on content that is complete.

Audited before clearing: each page has 8-10 sections, resolves all of its
example includes, has no unmigrated ReactJS content (the reactjs-ish grep hits
are HTML strings in parser prose and `import ReactPy`), no stub markers, and
all 24 example files under them compile. mkdocs build --strict passes and
reports no unresolved anchors.
Replaces the 2862-line ReactJS port with a ReactPy page plus ten examples
under docs/examples/choosing_the_state_structure/. The five React principles
survive; the four places where ReactPy semantics change the advice are stated
and measured rather than assumed.

Verified against a real Layout (26 checks, all passing) and, for the parts a
server cannot answer, in headless Chrome:

- use_state bails out on ==, so setting a brand-new dict/list that reads equal
  to the current value renders nothing (0 renders for {"items": [1,2]} over the
  same, 0 for [] over []). Mutating a list and "setting" it back leaves the
  screen at n=0 while the data grew -- three presses, one render.
- (1,2) vs [1,2] renders, because strictly_equal gates on type first, so the
  page tells readers to pick one container type per field.
- Grouping state removes the forgot-the-second-setter bug but saves no renders:
  two presses cost two renders both separate and grouped, one axis costs one.
  The page says this instead of claiming a performance win.
- Two booleans really can both be true: the buggy send ends at
  is_sending=True is_sent=True, confirmed in Chrome along with disabled
  behaviour on the textarea and the swap to the thank-you heading.
- A stored fullName goes stale when one writer forgets it; a derived one cannot.
  Both render once per press, so the argument is correctness, not renders.
- Duplicating the selected item as an object means editing the row leaves
  "You picked pretzels." while the derived version follows to "chips".
- The pitfall box is measured, not folklore: a plain class instance held as a
  use_effect dependency and mutated in place kept reporting n=0 across three
  presses while the screen showed n=3.

Exercises (Sandbox/Challenges) are dropped, matching the other authored pages.

Gates: hatch fmt --check (12 files), pyright 0 errors, reactpy-flake8 clean,
mkdocs build --strict with no unresolved anchors, no include-marker leaks, and
the verifier was mutation-tested -- storing fullName in state and clearing the
buggy flag each make the suite fail, then pass again on restore.
Removes the 🚧 badge from the sidebar, the page title, and the <h1>.
Replaces the 2640-line ReactJS port with a ReactPy page plus six examples
under docs/examples/extracting_state_logic_into_a_reducer/. The React teaching
arc (handlers describe intent, one pure function owns the transitions) is
kept; the parts where ReactPy semantics differ are stated and measured.

Verified against a real Layout (17 checks, all passing):

- ReactPy keeps the reducer from the FIRST render (use_reducer builds its
  dispatcher through _use_const). reducer_swapped shows a runtime swap
  changing the body but not the behaviour: version=2, by=add_by_one.
- The common consequence is a component-body reducer freezing its closure:
  reducer_frozen_value charges total=20 while the screen reads price=50.
  reducer_action_data is the fix -- module-level reducer, price in the action,
  verified reaching total=60.
- dispatch is eager, not queued to the next render as in React. Traced order
  for one click: handler:enter, reducer(add), handler:exit, render.
- A raising reducer is swallowed: state untouched, zero renders, nothing on
  the reactpy logger, only a root-logger "Failed to execute event handler".
  The page ends reducers with raise ValueError instead of a bare return state.
- Mutating and returning the same object compares equal, so it renders
  nothing: three presses, zero renders, no warning (reducer_mutates).
- Three dispatches in one handler apply in order in a single render.
- tasks_state and tasks_reducer verified behaviourally identical, including
  asserting the toggle flipped exactly the first row via the row style rather
  than its text -- a text check passed vacuously because the later delete
  removes that row.
- use_reducer takes two arguments; initial_value may be a zero-argument
  callable, called once (verified: built=1 across renders).
- The Deep Dive's frozen-dataclass advice is measured, not assumed:
  FrozenInstanceError on mutation, and strictly_equal compares instances
  field by field, so a no-op action renders nothing and a real change renders
  once. The page notes the raise lands in the server log, not on screen.

Examples place `# start` above the module-level constants and reducers so the
reader sees the reducer the prose discusses. Exercises are dropped, matching
the other authored pages.

Gates: hatch fmt --check (6 files), pyright 0 errors, mkdocs build --strict
with no unresolved anchors, no include-marker leaks, and the verifier was
mutation-tested -- toggling the wrong row, reading price from the action, and
copying instead of mutating each make it fail, then pass again on restore.
Replaces the 1088-line ReactJS port with a ReactPy page plus seven examples
under docs/examples/passing_data_deeply_with_context/. The React workflow
(create / use / provide) is kept; what the reference page does not cover is
measured here.

Verified against a real Layout (24 checks) and in headless Chrome:

- The prop-drilling and context versions render identical output -- same five
  rows, same coral->black toggle -- so the page can claim the swap is purely
  structural.
- With no provider, readers get the create_context default, not None.
- Nearest provider wins: four readers across nested providers resolve to
  default / outer / middle-provider / innermost as expected, and a sibling
  after a provider falls back to the default.
- A component cannot read the context it provides: the provider component
  itself reads the default while its child reads the provided value, before
  and after a toggle.
- Reading context in an argument expression above a provider reads the
  PARENT's context (the provider has not been entered yet). Encountered
  while writing the example and now taught as a pitfall.
- Removing a provider unmounts its subtree and discards state: the leaf's
  render counter resets to 1 and the typed draft='' returns empty, while
  changing only value= keeps both.
- The remedy is measured, not assumed. A stable key on the child does NOT
  prevent the remount (4 variants tested); keeping the provider element in
  both branches does. provider_always shows the working shape and is verified
  keeping draft='hello' with the counter still counting.
- Chrome confirms the provider contributes no DOM element (#places -> SECTION
  directly) and the styles really change (rgb(255,127,80) -> rgb(0,0,0)).

Examples place `# start` above the components the prose discusses, including
the draft() leaf the reader types into. Exercises dropped, matching the other
authored pages.

Gates: hatch fmt --check (7 files), pyright 0 errors, mkdocs build --strict
clean, no include-marker leaks, verifier mutation-tested -- un-nesting the
inner provider and making the provider unconditional each make it fail.
Rewrite the ReactJS page around ReactPy's use_reducer/create_context
semantics: dispatch is identity-stable because use_reducer builds it with
_use_const, so it is safe to carry through a context value. Adds three
runnable examples (prop-drilling, context, single-file wiring) that pass
an identical user-journey comparison, plus the provider-component and
use_tasks accessor pattern.
…Contributing Documentation

Write the four empty About pages from verified ground truth rather than
porting ReactJS text, which would have described a project layout this
repo does not have.

- community.md: Discussions-first routing (the issue template disables
  blank issues), the live Discord invite, sibling-repo pointers.
- running-tests.md: separates the hatch/pytest suite on main from the
  mkdocs gates on new-docs, and records that 'no tests ran' is a false
  pass on a docs-only branch.
- code.md: which branch a change belongs on, per-branch commit
  conventions, the shipped PR checklist, and the exact CI commands.
- docs.md: the port-not-rename rules (absent hooks, no StrictMode/memo,
  closure-inferred deps) and the docs/examples include workflow.

Claims checked against upstream origin/main, the installed 2.0.0b13
wheel, and live HTTP HEADs; the two harness traps in running-tests.md
were reproduced, not recalled.
The changelog page include-markdowns the root CHANGELOG.md between the
<!--changelog-start--> marker, but the 2023 docs-fork import reset that
file to an Unreleased stub with no markers, so the page rendered an empty
changelog for a library with 136 tags.

Restore the upstream release history into the root file and add the
attr/changelog markers the page's includes expect, retargeting the stale
[unreleased] compare link at the current reactpy-v2.0.0b13 tag. Add a
lead-in and a note pointing at GitHub Releases and PyPI as the
authoritative artefact lists.

Also drop the 🚧 badges for the five finished About pages.
new-docs ships exactly one workflow: pip install of docs/requirements.txt
then mkdocs gh-deploy --force on push. gh-deploy does not pass --strict,
so a build containing a broken internal link exits 0 and publishes it.
Prose implying a CI gate for docs pull requests promised a safety net
that does not exist; state instead that the contributor is the gate and
the live site is where a miss surfaces.
Rewrite the 2938-line ReactJS copy as a ReactPy tutorial: no JSX, no
useState, no export default, no CodeSandbox/React DevTools references,
and no React-only naming rules.

Split the single 115-line example into 12 staged examples under
docs/examples/tutorial_tic_tac_toe/, one per teaching step, so every
snippet on the page is an include from code that runs. The stylesheet is
now a single game.css (the two prior copies differed only by indentation).

Teach the ReactPy-specific parts React cannot:
- html.fragment for multiple children, and that a stray comma stringifies
  a tuple of VDOM dicts onto the page instead of raising.
- onClick must be camelCase; on_click registers server-side but no browser
  event has that name, so the control silently never fires.
- mutate-then-set-the-same-object schedules no render at all.
- key lives in the attribute dict for elements and as a keyword for
  components; a generator skips the key check entirely.
- ReactPy has no memo or render bailout, so React's immutability-for-
  re-render-skipping argument is presented as not applying here.

Verified with a Layout harness over all 12 examples (24 assertions), then
in headless Chrome over CDP: nine squares, stylesheet actually applied
(34px, flex row), alternating moves, jump-to-start rewinding the board,
and Winner: X on a completed line. Gates: hatch fmt --check, pyright
(0 errors across docs/), mkdocs build --strict.

Removed the 🚧 badge; no 🚧 markers remain in the nav.
…port claim

Both examples in AGENT_CONTEXT.md teach code that cannot run.

The provider was shown as theme_context({"value": "dark"}, html.div(...)).
create_context returns `context(*children, value=..., key=...)`
(core/hooks.py), so every positional argument is a child and there is no
attribute-dict slot: the dict becomes a child and the provider renders
ValueError: Expected a VDOM element dict, not {'value': 'dark'}. The
correct form is theme_context(child, value="dark"), which is what
reference/use-context.md and learn/passing-data-deeply-with-context.md
already use. Fixed here before it seeds the next migration pass.

`reactpy.hooks` was called a reachable submodule. It resolves as an
attribute (reactpy.hooks.use_state works) but is not a module: import
reactpy.hooks and from reactpy.hooks import use_state both raise
ModuleNotFoundError. Point imports at reactpy.core.hooks instead;
reactpy.utils is a real module and still imports fine.
…viour

Each of these was checked against reactpy 2.0.0b13 and the bundled client.

use_ref(): the page said the initial value is optional and that calling it
with no arguments leaves current unset. There is no no-argument form --
def use_ref(initial_value) (core/hooks.py) raises TypeError: use_ref()
missing 1 required positional argument. The AttributeError the page quoted
is real but belongs to reactpy.utils.Ref(), whose __init__ only assigns
current when given a value. Split into two paragraphs so the advice
(use_ref(None)) stays but carries the right reason.

extra_js: ReactPyCsr documented the dict as import name -> URL, which is
backwards. executors/asgi/pyscript.py and pyscript/utils.py
(js_modules["main"].update(extra_js)) make the key the URL and the value
the export name, reached as pyscript.js_modules.<name>. reference/jinja.md
already had it right; the two pages no longer contradict each other.

Raw HTML: usage.md claimed there is no way to hand ReactPy HTML and have it
parsed into VDOM. string_to_reactpy is exported at the top of the package
and does exactly that; learn/convert-between-vdom-and-html.md is dedicated
to it. The section now separates inserting raw HTML from parsing it.

dangerouslySetInnerHTML: common-props.md and usage.md said it is not allowed
alongside children. Nothing enforces that -- no schema constraint, the
layout emits both the attribute and the children, and the client assigns
innerHTML and clears the child list, so children vanish silently. Browser
verified: the element renders <b>RAW</b>, the child text is gone, and no
error is raised anywhere. Document the real silent-precedence behaviour
instead of an enforcement that does not exist.

protocol-structure.md listed attributes as forwarded to the DOM verbatim
including key. A keyed element renders only its id and class: the client
reads attributes.key as the vnode key and never writes it, and it deletes
_reactpy_ack_seq before render. The server does emit _reactpy_ack_seq
(core/layout.py), but only for input, select, and textarea.
Both pages told contributors that a page missing from nav: fails the strict
build. It does not: the build prints an INFO line listing the unlisted
pages and exits 0, and it still builds and serves them. Verified on this
branch -- the list currently holds eleven Planned / Undeveloped pages, all
built, all reachable by URL, none gated.

--strict is a real gate for two other things, confirmed by control tests
that were each shown to abort: a link whose target file does not exist, and
an unresolvable include-markdown. It also does not validate a #anchor
against an otherwise valid target file, which is a second surprise worth
naming, since a stale heading reads like a working link.

Reworded so the nav: list is described as something to read by eye rather
than a check the build performs, and so both gaps are explicit. This matters
more here than upstream because the only workflow on new-docs runs
mkdocs gh-deploy --force without --strict, so anything the local build lets
through goes live.
All three resolve to the wrong target today, each verified twice against
the live site.

about/licenses.md pointed at .../blob/main/LICENSE.md, which 404s. Upstream
main carries LICENSE with no extension; the rename to LICENSE.md happened
only on this branch (2bef10d), so the link was written against a file that
does not exist where it points. Link the repository root instead, which is
correct whether or not the rename ever merges upstream.

learn/quick-start.md linked two w3schools pages under a quick_start/ path
segment that no longer exists. Both topics live one level up at
/python/python_for_loops.asp and /python/python_lists_comprehension.asp,
confirmed by page title as well as status.

learn/tools-and-packages.md linked the Discussions category
third-party-packages, which does not exist. GitHub answers 200 and renders
"There are no matching discussions", so nothing looked broken; the real
categories are announcements, downloadable-packages, general, ideas,
problem, question, share. Retargeted at downloadable-packages, which is
what about/community.md already links for the same list.
react.dev's useId page was carried over under a Planned / Undeveloped banner
pointing at issue #985 as the thing that would deliver it. That issue was
closed as not_planned ("Closing since the original issue seemingly doesn't
have value to ReactPy"), with the author shipping use_root_id in
ReactPy-Django instead. "Planned" was no longer true of anything, and the
288-line body below the banner was still verbatim ReactJS -- JSX,
export default, <Pitfall> components, and absolute links to react.dev paths
that do not exist here.

The page was never in nav, not even commented out the way the other parked
pages are, so nothing referenced it: no nav entry, no link from any other
page, no mention in AGENT_CONTEXT.md, and no example directory. Deleting it
leaves no dangling target.

The about/docs.md line claiming every absent hook has a parked
reference/use-*.md page is now inaccurate for use_id and has been reworded.
learn/installation.md was tracked and built but unreachable: not in nav:, not
linked from any other page, and absent from the site navigation entirely.
Its content is already ReactPy-clean and its five links all resolve from the
built page, so this was an oversight rather than a page held back for work.

Added as Overview at the top of the existing Installation group in
Get Started, above the two pages it introduces. It no longer appears in the
build's pages-not-in-nav list, which now reports eleven pages, all of them
deliberately parked.
mkdocs-material 9.4.0 pins regex~=2022.4. That compatible-release bound caps
regex in the 2022 range, and no regex release in it ships a cp312 or cp313
wheel, so pip falls back to a source build that dies with
error: [Errno 2] No such file or directory: 'cc' on any machine without a C
compiler. The quickstart in README.md and about/running-tests.md both tell
people to install this file, so a current interpreter could not build the
docs at all.

Bumped to 9.7.7, the version the docs have actually been verified against.
Material dropped the upper bound in 9.5.0 (regex>=2022.4) and 9.7.x replaces
regex with backrefs entirely, so the pin no longer drags in an
unbuildable package.

Verified with a real install, not a resolver dry run -- uv's dry run happily
resolves the old pin because resolution is not buildability. Fresh 3.12 venv,
no compiler: pip install -r docs/requirements.txt exits 0 with regex absent
from the tree, and mkdocs build --strict against that venv exits 0.
Four real fixes, plus reformatting the whole stylesheet set to the
.editorconfig contract for *.css (indent_size = 4): admonition, banner,
button, footer, home, main, and navbar.css are whitespace-only changes and
are byte-identical after whitespace normalization, so the useful diff was
buried under tabs. The old mixed tab/space indentation came from 2bef10d.

The TOC heading was only uppercased under the sidebar selector, so it kept
material's default black and lost its casing cue outside that context.
.md-nav__title[for="__toc"] is now uppercased and muted to rgb(133,142,159)
directly, matching how the section is labelled everywhere else.

The secondary sidebar stayed visible once the left sidebar had collapsed
into the hamburger menu, leaving a lone right rail on tablet widths. It is
now hidden at max-width 76.1875em, the same breakpoint at which the primary
sidebar collapses.

Content was centred only inside the desktop media query, so narrower viewports
left it hanging on the left. The .md-content centring rules moved out to
apply at every width, and the sidebar nav buttons picked up the muted,
flat treatment: uppercase section titles, no background, no box-shadow, and
no leftover list indent. A --sizebar-font-size-2 token covers the smaller
nested level.

The copy button sat on a visible grey bar over the code block; .md-code__nav
is now transparent so it floats over the code instead.

Measured with headless Chrome over CDP, A/B against a build of HEAD so the
comparison is old CSS vs new CSS rather than reasoning from the diff:
.md-code__nav background rgba(245,245,245,0.3) -> transparent; TOC title
colour rgb(0,0,0) -> rgb(133,142,159); .md-content justify-self auto ->
center at 1100px; .md-sidebar--secondary display block -> none at 1100px
while it stays block at 1440px. prettier --check reports all ten files
clean, and prettier was confirmed to resolve indent_size 4 from
.editorconfig rather than its own 2-space default.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants