Repository navigation
Rewrite the docs - #1052
Draft
Archmonger wants to merge 131 commits into
Draft
Rewrite the docs#1052Archmonger wants to merge 131 commits into
Archmonger wants to merge 131 commits into
Conversation
rmorshea
reviewed
Jun 15, 2023
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Live preview: https://reactive-python.github.io/reactpy/
This PR is a rewrite of our documentation to imitate React's documentation. Additionally, this switches us from
sphinxtomkdocsas a documentation tool.Sections titles with a 🚧 symbol are still undeveloped.
Issues
use_contextdocs #845Potentially will cover