Skip to content

React frontend - #29

Merged
shikokuchuo merged 17 commits into
mainfrom
react-frontend
Sep 23, 2026
Merged

shikokuchuo merged 17 commits into
mainfrom
react-frontend

Conversation

@shikokuchuo

Copy link
Copy Markdown
Member

Closes #28. Uses the shinyreact package.

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.
@shikokuchuo
shikokuchuo merged commit 187ed31 into main Sep 23, 2026
7 checks passed
@shikokuchuo
shikokuchuo deleted the react-frontend branch September 23, 2026 19:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use shinyreact

1 participant