React frontend - #29
Merged
Merged
Conversation
Rebuild the amsync_app() and $edit() interfaces as a React frontend served
via shinyreact, removing bslib entirely. R/Shiny stays the sole owner of the
Automerge documents and all syncing; the browser is pure UI.
- New srcjs/ Vite build emits a self-contained IIFE to inst/www/amsync.{js,css}.
React/ReactDOM are externalized to window.shinyreact (single React 19
instance); target es2022 so @pierre/trees' private methods aren't down-leveled
to WeakSet brand-checks that minification mangles.
- File tree uses @pierre/trees (trees.software); editor uses CodeMirror 6 with
basicSetup (syntax highlighting, search, folding, autocomplete) and a per-
extension language map mirroring the old ext_to_language().
- build_amsync_app() server rewritten to reactive_output()/send_message();
install_editor_sync() generalized to a set_editor() callback. edit_in_shiny()
retargeted to the React "edit" view. All bslib UI builders deleted.
- Event inputs (buttons, file selection) and editor content are sent
imperatively via window.Shiny.setInputValue, not useSetShinyInput: shinyreact
re-broadcasts a hook's retained value on every (re)mount, which otherwise
auto-fired observers on load and after Disconnect, and clobbered the open
document with the editor's "" default.
- DESCRIPTION: Suggests shinyreact (Remotes posit-dev/shinyreact), drop bslib.
Update tests, docs, CLAUDE.md, and .Rbuildignore (^srcjs$).
Wrap the editor's Markdown language (.md/.qmd/.Rmd) with yamlFrontmatter so a
leading `--- ... ---` block is parsed and highlighted as YAML, and pass a
codeLanguages resolver so fenced code chunks highlight in their own language —
including knitr/Quarto's `{r}` / `{python}` brace syntax (and chunk options).
Drop the card chrome (border, header bar, footer, Bootstrap-blue button) that read as bslib in favour of a centered, airy layout: an "amsync." wordmark with an accent dot, a muted subtitle, refined rounded inputs with an accent focus ring, "Sign in" as a quiet link, a collapsed Advanced disclosure, a full-width near-black Connect, and a ghost Exit. Connect-specific styles use dedicated classes so the browse screen is untouched.
The bare text link read as non-interactive. Replace it with a full-width
secondary button ("Sign in" / "Re-authenticate") plus a helper caption that
shows "✓ signed in" once authenticated. Verified the click round-trips to
input$authenticate end-to-end.
The field value and placeholder inherited a heavier weight; pin them to 400 so the prefilled URL/ID and the grey placeholder hints read as normal text.
Simplify the connect flow: remove the standalone Sign in / Authenticate button and authenticate as part of Connect. When an OIDC client ID is provided (and no token is already held) the sign-in flow runs first, then connects with the token; with no client ID we connect tokenless for open servers. A failed sign-in keeps you on the connect screen. The OIDC fields live under Advanced with a note explaining the behaviour, and a subtle "✓ signed in" line shows only when a token is already held. Replace the two authenticate-button tests with auto-auth-on-connect coverage.
The first dial to a sync server (often right after signing in) can fail transiently (e.g. "Protocol error" / "Permission denied"). Wrap the connect step in connect_with_retry(): retry up to 5 times, one second apart, notifying "Connection failed, retrying (n/5)…" between attempts and only reporting the error if all attempts fail. The 1s pause (retry_pause) drives the event loop so the wait stays responsive and is stubbed in tests.
Add a FileIcon component beside the open file's name: .qmd files get the official Quarto glyph (Simple Icons path, brand colour #39729E); other files get a neutral document icon so the header stays consistent.
Inject the Quarto glyph as a sprite symbol into @pierre/trees and map the .qmd extension to it via byFileExtension, keeping the built-in "complete" icon set for all other files (set must be given explicitly, since any custom override otherwise flips the default set to "none"). Reuses QUARTO_PATH from FileIcon.
The retry notification string had a literal "…" (U+2026) in code, which R CMD check flags as non-ASCII. Replace it with the … escape; the toast still renders an ellipsis at runtime.
Rename the connect-screen brand text from "amsync." to "autosync." (the amsync-* CSS class names are unchanged).
- Regenerate NAMESPACE with roxygen2: export amsync_app()/amsync_project(), register print.amsync_project, import am_text_content()/am_text_update() and tools::file_ext() for the restored app layer. - Demote [install_editor_sync()] / [edit_in_shiny()] roxygen links to plain code (internal helpers, not linkable topics). - Update a stale amsync_token() reference in a ConnectScreen comment.
Aligns the restored app layer with main's sync_* naming convention: sync_app(), sync_project(), build_sync_app(), class autosync_project with print.autosync_project. The inst/www asset names and amsync_react_dep() keep the "amsync" name of the shipped JS bundle (comment-only srcjs changes, so no bundle rebuild needed).
shinyreact 0.1.0's page_react() always builds its own dependency from
src_dir (default "www", resolved against the working directory) and aborts
when it does not exist, so passing a hand-wired page_react_dep() while
leaving src_dir at its default broke sync_app() and $edit() from any
directory without a www/ folder.
Point page_react() straight at the shipped bundle (src_dir =
system.file("www"), js_file/css_file = amsync.{js,css}), replacing
amsync_react_dep() with amsync_react_page(), and require
shinyreact (>= 0.1.0) in Suggests.
shinyreact 0.1.0's page_react() serves a bare page with no mount container
(the app must append its own, per the shinyreact-build-app skill). The
entry's getElementById("root") therefore found nothing and the guard
silently skipped rendering, leaving a blank gadget. Append and render into
our own div instead; styles.css keeps its #root selector. Bundle rebuilt.
sync_app() published OIDC_CLIENT_ID/OIDC_CLIENT_SECRET values to the
browser via output$init just to prefill the sign-in form, so the secret
left the R session on every page load. The init payload now carries flags
recording only that the env vars are set, the connect observer falls back
to the env vars server-side when the fields are blank, and the form
advertises the fallback in placeholder text ("Set from $OIDC_CLIENT_ID -
leave blank to use").
Test env handling uses the vendored withr standalone defer()
(R/import-standalone-defer.R, via usethis::use_standalone) rather than a
withr dependency; connect-related tests set/clear the OIDC env vars with a
local_oidc_env() helper so they are hermetic regardless of the developer's
shell environment.
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.
Closes #28. Uses the
shinyreactpackage.