Conversation
…world placeholders Co-authored-by: schloerke <93231+schloerke@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Rename occurrences of shinchat to shinyjson and remove logic files
Initialize shinyjson from shinychat with renames and hello world placeholders
Mar 2, 2026
schloerke
marked this pull request as ready for review
March 2, 2026 20:16
Collaborator
|
Closing as implemented locally |
schloerke
added a commit
that referenced
this pull request
May 14, 2026
WeakKeyDictionary keyed only on the component instance kept the same reactive.calc alive across sessions. Module-level components see many sessions; the calc bound to session #1 is destroyed when that session ends, and session #2 hit a DestroyedReactiveError when re-using the cached calc -> grey 'Disconnected' overlay. Switch to a per-instance attribute that stores (session_obj, calc). If the captured session differs from the current one, recreate the calc.
schloerke
added a commit
that referenced
this pull request
May 14, 2026
* docs(specs): shinyui metadata-consolidation prototype design (#69) Stage A design for umbrella #68 / issue #69. Specs a new sibling Python package `shinyui` at pkg-py/src/shinyui/ that prototypes a class-per-component UI hierarchy with seven concrete archetypes, resolves the three umbrella open questions (handler registration, bookmark lookup, update signature), and refactors the umbrella's UiInput/UiLayout straddler into orthogonal HasInputValue + Updatable mixins so layouts-with-state read honestly. * docs(plans): shinyui metadata-consolidation implementation plan (#69) * feat(shinyui): scaffold sibling package + test infra * style(shinyui): ruff format conftest and smoke test * feat(shinyui): UiComponent base + session/read helpers * feat(shinyui): AllowsChildren mixin Adds AllowsChildren mixin (pkg-py/src/shinyui/_children.py) with children list, append(), __enter__/__exit__ overrides. Also fixes UiComponent.__init__ to forward *args cooperatively so AllowsChildren receives positional children when MRO order is MyComp(UiComponent, AllowsChildren). * feat(shinyui): per-session id->instance registry * feat(shinyui): HasInputValue mixin + handler registration * feat(shinyui): Updatable abstract mixin * feat(shinyui): UiInput / UiOutput / UiLayout role classes * feat(shinyui): local reactive_calc_method helper * feat(shinyui): UiInputSlider + input_slider() factory * feat(shinyui): UiInputSelect + input_select() factory * feat(shinyui): UiOutputCode + output_code() factory * feat(shinyui): UiOutputPlot with read-only signal accessors * feat(shinyui): UiAccordionPanel + accordion_panel() factory * feat(shinyui): UiAccordion + accordion() factory * feat(shinyui): UiCard with full_screen_value() and update() * feat(shinyui): public exports + accordion pyright fixes * test(shinyui): cross-cutting hierarchy + lifecycle tests * test(shinyui): bookmark id->instance round-trip * feat(shinyui): lookup_component + example app exercising the full reference set * fix(shinyui): recursively tagify subtrees in container classes htmltools' built-in Tag.tagify walks only one level: it replaces direct Tagifiable children with their tagify() result but does not recurse into the resulting Tag's own children. Containers whose tagify wraps un-resolved Tagifiable descendants therefore reached htmltools' rendering layer with nested Tagifiables, triggering 'non-tagified object' at render time. Add UiComponent._deep_tagify(node) — a small recursive helper that walks Tag/TagList children, calling tagify() on every Tagifiable it encounters. Apply it in UiCard and UiAccordion (the two containers whose children can themselves be Tagifiable). The example app now renders successfully. * fix(shinyui,example): card full_screen reads _full_screen suffix; module-level demo components - UiCard.full_screen_value() now reads input.<id>_full_screen (matches Shiny's card binding wire format) instead of input.<id>. Update card and read-accessors tests to match. - Example app 14 constructs components at module level so closures share them between app_ui (HTTP phase, no session) and server (WebSocket phase, session bound). The lookup_component approach in the previous version returned None during HTTP-phase construction, crashed the session, and showed the grey 'Disconnected' overlay. - Replace matplotlib placeholder with a PIL solid-color image so the example runs without matplotlib in the venv. * feat(example): visible plot placeholder for app-py/14 Solid-color placeholder was indistinguishable from the card background. Added a border, crosshair, and label text via PIL.ImageDraw so the plot output has a clear visual target for click and brush interactions. * chore: gitignore stray screenshot from local playwright run * fix(shinyui): cross-session reactive_calc_method cache invalidation WeakKeyDictionary keyed only on the component instance kept the same reactive.calc alive across sessions. Module-level components see many sessions; the calc bound to session #1 is destroyed when that session ends, and session #2 hit a DestroyedReactiveError when re-using the cached calc -> grey 'Disconnected' overlay. Switch to a per-instance attribute that stores (session_obj, calc). If the captured session differs from the current one, recreate the calc. * feat(example): real scatter plot driven by slider/select/seed reads * feat(example): two-button accordion control; sample/dist/seed live in Settings panel * feat(example): move summary code block into Diagnostics accordion panel * feat(shinyui): UiInputActionButton class + integrate into example 14 UiInputActionButton(UiInput, Updatable) ships the same shape as the other input classes: - typed __init__ mirroring shiny.ui.input_action_button (label, icon, width, disabled) - .count() reactive accessor exposing the click counter (0 before first click) - typed update(*, label, icon, disabled) delegating to shiny.ui.update_action_button - factory function input_action_button(...) - 6 unit tests pinning factory/snapshot/count/update behavior Example 14 swaps ui.input_action_button for su.input_action_button on both Open-all and Close-all controls and wires the @reactive.event deps via btn.count instead of input.<id>, so the demo's button surface is fully class-based now. * feat(shinyui): rename UiOutputPlot.dblclick_value -> dbl_value Matches the shorter accessor name and reads the same _dblclick wire suffix. Add module-docstring note that limits_value / selection_value accessors are intentionally absent: shiny.ui.output_plot only pushes the four documented interaction signals (click, dblclick, hover, brush). If shiny gains _limits or _selection upstream, add the accessors then. * feat(example): rewrite example 14 as a Shiny Express app Switch from def app_ui(request) / def server() to a top-level expressify script. Construct shinyui components programmatically (factory calls with children as args) since AllowsChildren is not wired into Express's RecallContextManager yet — sub-issue 3 territory. Wrap the @render decorators in 'with ui.hold():' to suppress Express's auto-placement; the renderers still register with the session by id, binding to the output_code / output_plot elements we placed inside the accordion. Without hold(), Express would inject duplicate <pre id=...> elements at the page tail. Use plain shiny.ui.layout_column_wrap (imported as _sui) for the inline button row; shiny.express.ui.layout_column_wrap is the recall-context variant that takes 0 positional args. * feat(shinyui): rename UiInputActionButton.count -> clicked Reads more naturally at the call site ("btn.clicked() > 0" vs the slightly ambiguous "btn.count() > 0"). Wire suffix unchanged (reads input.<id> directly). Tests and example 14 updated. * feat(shinyui): demo __init_subclass__ handler registration on UiInputActionButton Most shinyui classes register input handlers via an explicit cls._register_input_handler() call at module load. The action button now demonstrates the alternative: a small _InputHandlerAutoRegister mixin whose __init_subclass__ hook auto-fires registration when the class is defined. Concretely: - Add input_handler_name = 'shinyui.action' and a small _input_handler staticmethod that coerces wire value -> int (parallel of py-shiny's 'shiny.action' handler). - Register under 'shinyui.action' (not 'shiny.action') so we don't collide with shiny's built-in; the demo's purpose is the registration mechanism, not real wire traffic — shiny's markup still routes action-button events through its own 'shiny.action' handler. - Pin behavior with two new tests: registry contains 'shinyui.action' after import, and _input_handler returns plain ints. Update the cross-cutting test_input_handler_registration.py to reflect the exception. * refactor(shinyui): drop _deep_tagify; rely on .tagify() chain resolution htmltools' Tag.tagify() iterates Tagifiable->Tagifiable chains inside its single-level TagList walk, so calling .tagify() once on the outer Tag is enough to fully resolve our Tagifiable descendants. The custom recursive _deep_tagify helper I'd added is unnecessary. UiAccordion still pre-resolves its children via [c.tagify() for c in ...] because shiny.ui.accordion does an explicit isinstance(panel, AccordionPanel) check on positional args and rejects UiAccordionPanel (which is Tagifiable but not an AccordionPanel subclass). UiCard has no such isinstance check, so it hands its children in unchanged and lets the outer .tagify() resolve them. Both files document the rule inline; the module-docstring of _base.py adds a one-line guidance pointer for future container subclasses. * refactor(shinyui): move __init_subclass__ auto-register onto HasInputValue The standalone _InputHandlerAutoRegister mixin duplicated logic that HasInputValue already owns (input_handler_name, _input_handler, _register_input_handler classmethod). Move the __init_subclass__ hook onto HasInputValue itself — now every HasInputValue subclass auto-fires registration on class-definition. Classes with the defaults (input_handler_name='' or _input_handler is None) skip silently, so this is no-op for slider/select/card/accordion and active only for UiInputActionButton. Drops the extra base from UiInputActionButton's MRO and removes ~20 lines of plumbing. The action-button module docstring still explains the demo and the 'shinyui.action' vs 'shiny.action' separation. * refactor(shinyui): rename concrete classes to snake_case Match shiny.render.* convention (e.g. shiny.render.data_frame) by naming concrete user-facing classes in snake_case. Drops the parallel factory functions since the class name now equals the call-site name — no more double record keeping. Bases and mixins (UiComponent, UiInput, UiOutput, UiLayout, HasInputValue, Updatable, AllowsChildren) stay PascalCase, mirroring shiny.render.Renderer. Class rename map: UiInputSlider -> input_slider UiInputSelect -> input_select UiInputActionButton -> input_action_button UiOutputCode -> output_code UiOutputPlot -> output_plot UiCard -> card UiAccordion -> accordion UiAccordionPanel -> accordion_panel Docs (spec + plan) updated to reflect the new names; the older 2026-05-06 umbrella spec is left alone since it describes the original design vision. * feat(shinyui): add Express/Core overloads to container classes The three classes with AllowsChildren (card, accordion, accordion_panel) now expose two @overload signatures on __init__: 1. Express overload (no positional children) — listed first so IDEs prefer it when the user writes `with card(id=...) as c: ...`. 2. Core overload (positional children) — for inline construction like `card(child_a, child_b, id=...)`. The runtime __init__ is unchanged; the overloads are pure type hints for IDE / pyright consumption, mirroring the umbrella spec's sub-issue 2 plan for Core/Express signature unification. * docs(shinyui): add class + __init__ docstrings to concrete UI classes Each of the eight concrete classes (input_slider, input_select, input_action_button, output_code, output_plot, card, accordion, accordion_panel) now ships: - A class-level docstring naming the wire id, the matching class accessor (if any), and a small idiomatic Example block. - An __init__ docstring documenting id/label (or title), the key shape parameters, and a pointer to shiny.ui.<name> for the long tail of pass-through kwargs. Pure documentation — no signature or runtime behaviour changes. input_slider already had this treatment; the others now match its template. * refactor(shinyui): accordion_panel.tagify() returns Tag; drop type:ignore Honor the UiComponent.tagify() -> Tag contract on accordion_panel by chaining .tagify() on shiny's AccordionPanel wrapper. The previous override that returned AccordionPanel (a Tagifiable, not a Tag) needed a # type: ignore[override] annotation; that's now gone. Two coupling points required care: - shiny.ui.accordion does an explicit isinstance(panel, AccordionPanel) check on its positional args, so the parent accordion can't consume our rendered Tag. Add a small private helper _build_accordion_panel() that returns the AccordionPanel wrapper; the accordion's tagify() uses that helper instead of calling child.tagify(). - shiny's AccordionPanel.tagify() raises if _accordion_id is not set (normally written by the parent accordion). For standalone rendering (snapshot tests, ad-hoc inspection), stamp a placeholder id keyed off the panel's value before calling .tagify(). The parent path is unaffected — it gets a fresh AccordionPanel each time via the helper. Snapshot test for accordion_panel switched from attribute comparison (_title, _args, _data_value, _icon) to asserting the returned object isinstance(Tag) with the title and body content present in the rendered HTML. * docs(shinyui): move container docstrings onto @overload signatures card, accordion, and accordion_panel each have an Express overload (no positional children, used in 'with X():' blocks) and a Core overload (inline positional children). Move the doc + Example block onto each overload separately so IDE tooltips show the right idiom for the call site. The implementation __init__ at the bottom of each class loses its docstring (overload stubs carry the docs now). * refactor(shinyui): accordion_panel.tagify() returns Tag; drop _build_accordion_panel helper Honor the UiComponent.tagify() -> Tag contract on accordion_panel by chaining .tagify() on shiny's AccordionPanel wrapper. Stamp a placeholder _accordion_id so standalone .tagify() works outside a parent accordion (shiny's AccordionPanel.tagify() raises if _accordion_id is unset). The parent accordion now builds AccordionPanel wrappers inline from each child's stored title/children/_value/icon — rather than calling a helper method on the child — since shiny.ui.accordion does an isinstance check on positional args and rejects rendered Tags. No _build_accordion_panel indirection. * style(shinyui): package-wide N801 ignore; drop per-line noqa pragmas Concrete shinyui classes are intentionally snake_case (matching shiny.render.data_frame's convention — class name == call-site name, no parallel factory functions). Configure ruff to ignore N801 ('class name should use CapWords') package-wide for pkg-py/src/shinyui in pyproject.toml, and remove the eight per-line '# noqa: N801' pragmas the concrete classes carried. * docs: README + spec fixes from Copilot PR review Addresses PR #100 review threads from Copilot: - examples/.../README.md: full rewrite for the current Express app — drops the stale lookup_component snippet, the removed _auto_expand_at_high_n effect, and the n>800 'What to try' bullet. Adds the Open all / Close all button section that's actually wired in app.py. Corrects the card full_screen wire id to _full_screen. - docs/.../spec: 'seven concrete classes' -> 'at least seven', and the lifecycle bullet for handler registration now describes the __init_subclass__ approach actually used (HasInputValue auto-fires cls._register_input_handler() on subclass creation), with input_action_button called out as the demonstrating class. - pkg-py/src/shinyui/_card.py: module docstring corrected to say the accessor reads input.<id>_full_screen (the actual wire suffix) — the previous text contradicted full_screen_value()'s implementation. - pkg-py/src/shinyui/_reactive.py: implement the on_ended cache eviction the module docstring already promised. The cached (session, calc) attribute is now dropped proactively when the captured session ends, before the lazy mismatch-detection at the next accessor call would have replaced it.
4 tasks
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.
Bootstraps the
shinyjsonrepo usingposit-dev/shinychatas the template, renames allshinychatreferences toshinyjson, and strips business logic from R/Python/JS packages leaving minimal hello world placeholders.Changes
Repository structure (from shinychat)
LICENSE,Makefile,pyproject.toml,.gitignore,shinyjson.Rproj.github/workflows/: R CMD check, pkgdown, py-check, py-release, quartodoc, verify-js-builtjs/: build toolchain (esbuild, eslint, tsconfig, package.json)pkg-py/: Python package scaffoldpkg-r/: R package scaffoldRenames
All
shinychat→shinyjsonthroughout file contents and filenames (package names, URLs, descriptions, Makefile targets, DESCRIPTION, pyproject.toml, NAMESPACE, etc.).Business logic → hello world placeholders
R — all chat logic removed; single placeholder:
Python — all
_chat*.pyremoved; minimal package:JavaScript — chat/markdown-stream/utils removed; single entry point:
build.tsupdated to referencesrc/shinyjson/shinyjson.{ts,scss}instead of the removed chat/markdown-stream entries.Security
Added
permissions: contents: readtoverify-js-built.yamlto scopeGITHUB_TOKENto minimum required.Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.