diff --git a/AGENTS.md b/AGENTS.md index 0180a1f..8b668a2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,9 +4,9 @@ This file provides guidance to AI coding agents (Claude Code, etc.) when working ## What this is -`reactlog` is an R package that visualizes Shiny's reactive dependency graph. A small R side (`R/`) injects Shiny's recorded log into an HTML page; a Flow-typed JavaScript app (`srcjs/`) replays it as a cytoscape graph. +`reactlog` is an R package that visualizes Shiny's reactive dependency graph. A small R side (`R/`) injects Shiny's recorded log into an HTML page; a TypeScript app (`srcts/`) replays it as a cytoscape graph. -The built bundle `inst/reactlog/reactlogAsset/reactlog.js` (and its `.map`) **is committed**. CRAN and `R-CMD-check` never run Node. If you change anything in `srcjs/`, rebuild and commit the bundle with your change (the shared `routine` CI job also runs `npm ci` + `npm run build` and pushes any bundle changes back to same-repo PRs). +The built bundle `inst/reactlog/reactlogAsset/reactlog.js` (and its `.map`) **is committed**. CRAN and `R-CMD-check` never run Node. If you change anything in `srcts/`, rebuild and commit the bundle with your change (the shared `routine` CI job also runs `npm ci` + `npm run build` and pushes any bundle changes back to same-repo PRs). ## Commands @@ -14,15 +14,14 @@ JavaScript (npm; Node 22 works): ```bash npm ci # install; postinstall also runs the build -npm run build-only # webpack build via grunt -> inst/reactlog/reactlogAsset/reactlog.js -npm run build # eslint --fix + prettier on srcjs, then build -npm run watch # lint, then rebuild on change -npm run lint -- srcjs/foo.js # eslint --fix + prettier on given files -npm run flow-report # flow type coverage (95% threshold, see package.json) +npm run build-only # esbuild -> inst/reactlog/reactlogAsset/reactlog.js (no type check) +npm run typecheck # tsc, no output files +npm run build # eslint --fix + prettier on srcts, typecheck, then build +npm run watch # lint, then rebuild on change (no type check) +npm run lint -- srcts/foo.ts # eslint --fix + prettier on given files +npm run check-shiny-target # fail if tsc/esbuild target differs from rstudio/shiny (needs network) ``` -`build-only` and `watch` set `NODE_OPTIONS=--openssl-legacy-provider` because webpack 4 needs it on Node 17+. - R: ```r @@ -34,7 +33,7 @@ devtools::document() # regenerate man/ and NAMESPA devtools::check() ``` -`npm ci` installs a git pre-commit hook (`pre-commit` + `lint-staged`, config in `package.json`). On commit it lints staged `R/*.R` with `devtools::lint()` (needs the `lintr` R package) and, for staged `srcjs/**/*.js`, runs lint + build, the flow report, and `git add`s the rebuilt bundle. +`npm ci` installs a git pre-commit hook (`pre-commit` + `lint-staged`, config in `package.json`). On commit it lints staged `R/*.R` with `devtools::lint()` (needs the `lintr` R package) and, for staged `srcts/**/*.ts`, runs lint, typecheck, and build, then `git add`s the rebuilt bundle. ## Memory Bank @@ -52,10 +51,10 @@ the `!/.agents/skills//` lines in `.gitignore` to match. `memory-bank/architecture.md` explains how the R side, the HTML template, and the JavaScript app connect, plus how to work on the JS without running Shiny. -Read it before working in `R/` or `srcjs/`. +Read it before working in `R/` or `srcts/`. ## Conventions -- JS files start with `// @flow`. Lint is eslint 5 + prettier 1 (`.eslintrc.js`, `prettier.config.js`). +- Source is TypeScript (`tsconfig.json`, `strict` off for now). Lint is eslint 10 + typescript-eslint + prettier 3 (`eslint.config.mjs`, `prettier.config.js`). - Add a NEWS.md entry for user-facing changes (CONTRIBUTING.md). - New top-level files need an entry in `.Rbuildignore`, or R CMD check will warn. diff --git a/README.md b/README.md index 0549a75..1738e72 100644 --- a/README.md +++ b/README.md @@ -96,10 +96,9 @@ For bug reports, please use the **reactlog** [issue tracker](https://github.com/ ## Development [![node version: 22.x](https://img.shields.io/badge/node-22.x-brightgreen.svg)](https://nodejs.org/en/) -[![JavaScript type: flow](https://img.shields.io/npm/types/flow-typed.svg)](https://github.com/flow-typed/flow-typed) +[![language: TypeScript](https://img.shields.io/badge/language-TypeScript-3178c6.svg)](https://www.typescriptlang.org/) [![cytoscape](https://img.shields.io/github/package-json/dependency-version/rstudio/reactlog/cytoscape.svg)](https://js.cytoscape.org/) -[![@babel/preset-env](https://img.shields.io/github/package-json/dependency-version/rstudio/reactlog/dev/@babel/preset-env.svg)](https://babeljs.io/) -[![webpack](https://img.shields.io/github/package-json/dependency-version/rstudio/reactlog/dev/webpack.svg)](https://webpack.js.org/) +[![esbuild](https://img.shields.io/github/package-json/dependency-version/rstudio/reactlog/dev/esbuild.svg)](https://esbuild.github.io/) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) [![linter: eslint](https://img.shields.io/badge/linter-eslint-3524ca.svg)](https://eslint.org/) diff --git a/memory-bank/architecture.md b/memory-bank/architecture.md index 455b42b..b71aaad 100644 --- a/memory-bank/architecture.md +++ b/memory-bank/architecture.md @@ -1,6 +1,6 @@ --- title: "Architecture" -description: "How reactlog fits together: Shiny's recorded log, the R functions that inject it into an HTML template, and the JavaScript app that replays it as a cytoscape graph. Start here before working in R/ or srcjs/." +description: "How reactlog fits together: Shiny's recorded log, the R functions that inject it into an HTML template, and the JavaScript app that replays it as a cytoscape graph. Start here before working in R/ or srcts/." --- # Architecture @@ -8,7 +8,7 @@ description: "How reactlog fits together: Shiny's recorded log, the R functions `reactlog` has two halves: - **R package** (`R/`): small. It takes the log that Shiny records while `options(shiny.reactlog = TRUE)` is set, turns it into JSON, and injects it into an HTML page. -- **JavaScript app** (`srcjs/`): most of the code. A Flow-typed, webpack-bundled single page app that replays the log step by step and draws the reactive graph with cytoscape + dagre. +- **JavaScript app** (`srcts/`): most of the code. A TypeScript single page app, bundled with esbuild, that replays the log step by step and draws the reactive graph with cytoscape + dagre. The built bundle `inst/reactlog/reactlogAsset/reactlog.js` (and its `.map`) is committed. CRAN and `R-CMD-check` never run Node; the shared `routine` CI job runs `npm ci` + `npm run build` and commits any bundle changes back to the PR. @@ -23,14 +23,14 @@ The built bundle `inst/reactlog/reactlogAsset/reactlog.js` (and its `.map`) is c 3. `reactlog_show()` opens that file in a browser. 4. `reactlog_module_ui()` / `reactlog_module_server()` (`R/shinyModule.R`) embed the viewer as an iframe that points at Shiny's own `reactlog?w=...&s=...` endpoint. The module needs the shiny version listed in DESCRIPTION `Suggests`, which `shiny_version_required()` reads at runtime, so keep that field accurate. -## Browser side (`srcjs/`) +## Browser side (`srcts/`) -Entry point is `srcjs/index.js`, which wires DOM elements from `reactlog.html` to the modules below on page load. +Entry point is `srcts/index.ts`, which wires DOM elements from `reactlog.html` to the modules below on page load. -- `log/initStep.js` runs first on import. It takes `window.__APP_DATA__` (or `window.log` from `defaultLog.js`) and gives each entry `step = index`. `log/logStates.js` lists every log `action` and its Flow types. -- `rlog.js` is a shared mutable singleton (also `window._reactlog`) holding the log, the cytoscape instance, the `GraphAtStep`, and `curTick`. Most modules read and write state through it. -- `graph/Graph.js` is the graph state built by applying log entries in order with `addEntry()`: nodes, edges, ghost edges, statuses, family-tree queries, filtering. -- `graph/GraphAtStep.js` is the time-travel layer. It indexes special steps (idle, output calc, user marks, async) and rebuilds a `Graph` for any step by replaying the log from the start (`rawGraphAtStep`; the cache is commented out). `displayAtStep()` pushes the result into cytoscape. +- `log/initStep.ts` runs first on import. It takes `window.__APP_DATA__` (or `window.log` from `defaultLog.js`) and gives each entry `step = index`. `log/logStates.ts` lists every log `action` and its TypeScript types. +- `rlog.ts` is a shared mutable singleton (also `window._reactlog`) holding the log, the cytoscape instance, the `GraphAtStep`, and `curTick`. Most modules read and write state through it. +- `graph/Graph.ts` is the graph state built by applying log entries in order with `addEntry()`: nodes, edges, ghost edges, statuses, family-tree queries, filtering. +- `graph/GraphAtStep.ts` is the time-travel layer. It indexes special steps (idle, output calc, user marks, async) and rebuilds a `Graph` for any step by replaying the log from the start (`rawGraphAtStep`; the cache is commented out). `displayAtStep()` pushes the result into cytoscape. - `updateGraph/` holds the navigation actions (next/prev step, idle, output calc, user mark, search, hover/sticky filter). They all end in `atTick()`, which redraws the graph, progress bar and log entry panel. - `cyto/` has cytoscape setup, styles and event handlers. `layout/` has DOM pieces (keyboard shortcuts, progress bar, log entry panel). @@ -50,5 +50,5 @@ The pkgdown site (`pkgdown/`, built by `.github/shiny-workflows/website.R`) copi | [graphReplay.md](graphReplay.md) | `Graph` / `GraphAtStep`, node and edge status, filtering, the cytoscape diff and layout | | [viewerUi.md](viewerUi.md) | Page DOM, navigation actions, keyboard shortcuts, timeline, status panel | | [shinyIntegration.md](shinyIntegration.md) | How shiny calls reactlog, the `/reactlog` endpoint, the module, tests | -| [jsBuild.md](jsBuild.md) | npm / grunt / webpack 4 toolchain, Flow, lint, the pre-commit hook | +| [jsBuild.md](jsBuild.md) | npm / esbuild / TypeScript toolchain, lint, the pre-commit hook | | [ciAndRelease.md](ciAndRelease.md) | GitHub Actions, the pkgdown site, CRAN release files | diff --git a/memory-bank/ciAndRelease.md b/memory-bank/ciAndRelease.md index 6b234f7..d265711 100644 --- a/memory-bank/ciAndRelease.md +++ b/memory-bank/ciAndRelease.md @@ -9,13 +9,14 @@ All CI lives in one workflow file that calls three shared jobs from [rstudio/shiny-workflows](https://github.com/rstudio/shiny-workflows) at tag `@v1`. This repo only adds one hook script, `.github/shiny-workflows/website.R`. For how the R and JS halves fit together, see [architecture.md](architecture.md). -For the Node/webpack toolchain, see [jsBuild.md](jsBuild.md). +For the Node/esbuild toolchain, see [jsBuild.md](jsBuild.md). Key files: | File | Role | | --- | --- | -| `.github/workflows/R-CMD-check.yaml` | The only workflow ("Package checks"). Calls `website`, `routine`, `R-CMD-check` from shiny-workflows `@v1` | +| `.github/workflows/R-CMD-check.yaml` | "Package checks". Calls `website`, `routine`, `R-CMD-check` from shiny-workflows `@v1` | +| `.github/workflows/shiny-target.yaml` | "Shiny build target". Runs `npm run check-shiny-target` on build-config PRs and weekly (see [jsBuild.md](jsBuild.md)) | | `.github/shiny-workflows/website.R` | Hook run by the `website` job before pkgdown builds. Copies the demo and readme gifs into `pkgdown/assets/` | | `pkgdown/_pkgdown.yml` | pkgdown config: site URL, docsearch, `development: mode: auto` | | `pkgdown/favicon/` | Site favicons | @@ -90,7 +91,7 @@ Steps that may auto-commit, in order: | `usethis::use_tidy_description()` | Reorders and reformats `DESCRIPTION` | | `devtools::document()` | `DESCRIPTION`, `NAMESPACE`, `man/` | | `oxipng` | Lossless PNG shrink on `man/figures/*.png` and `vignettes/*.png`. Git pathspec `*` also matches `/`, so this hits all 22 PNGs under `vignettes/images/` | -| `npm ci` + `npm run build` | Uses Node `current`. `package.json` has a `build` script, so this runs (`npm ci` also runs `postinstall`, which builds). `build` runs `lint-all` (eslint `--fix` + prettier on `srcjs/`) then webpack. Commits changes under `inst/` and `srcjs/` plus `package-lock.json` | +| `npm ci` + `npm run build` | Uses Node `current`. `package.json` has a `build` script, so this runs (`npm ci` also runs `postinstall`, which builds). `build` runs `lint-all` (eslint `--fix` + prettier on `srcts/`), `typecheck` (tsc), then esbuild. Commits changes under `inst/` and `srcts/` plus `package-lock.json` | There is no `README.Rmd`, `codecov.yml`, or `# @staticimports`, so those steps skip. After pushing, it runs `devtools::lint()` (because `.lintr` exists) and fails on @@ -169,7 +170,7 @@ will otherwise keep rewriting. ## `.Rbuildignore` -Keeps out of the tarball: `srcjs`, `bin`, `node_modules`, JS config files, +Keeps out of the tarball: `srcts`, `bin`, `node_modules`, JS config files, `package.json`/`package-lock.json`, `*.js.map`, `inst/log-files`, `inst/gantt`, `readme-images`, `logo`, `pkgdown`, `revdep`, `.github`, `cran-comments.md`, `AGENTS.md`, `.agents`, `.claude`, `memory-bank`, @@ -184,9 +185,8 @@ here. release tarballs from a clean clone. - `routine` pushes commits to your PR branch. Pull before pushing again, or you will get a non-fast-forward error. -- A `srcjs/` lint problem can turn into a `routine` commit (eslint `--fix`), or - fail `npm run build`. Node `current` plus webpack 4 needs - `--openssl-legacy-provider`; keep it in `build-only`. +- A `srcts/` lint problem can turn into a `routine` commit (eslint `--fix`), or + fail `npm run build`. A type error also fails `npm run build`. - Do not move `website.R` output back to `pkgdown/`. pkgdown 2.x ignores it and the demo and gifs vanish from the site with no CI error. - `pkgdown/assets/` is generated and gitignored. Do not commit it. diff --git a/memory-bank/graphReplay.md b/memory-bank/graphReplay.md index 6d523f7..21060d5 100644 --- a/memory-bank/graphReplay.md +++ b/memory-bank/graphReplay.md @@ -1,6 +1,6 @@ --- title: "Graph replay" -description: "How the JS app turns the log into a graph at any step: Graph.addEntry() per action, Node/Edge/GhostEdge, status objects and how they map to cytoscape classes and colors, GraphAtStep step indexes, hover/sticky/filter/search, and displayAtStep() diffing plus the dagre layout. Read before touching srcjs/graph/, srcjs/cyto/, or anything that changes what the graph shows at a step." +description: "How the JS app turns the log into a graph at any step: Graph.addEntry() per action, Node/Edge/GhostEdge, status objects and how they map to cytoscape classes and colors, GraphAtStep step indexes, hover/sticky/filter/search, and displayAtStep() diffing plus the dagre layout. Read before touching srcts/graph/, srcts/cyto/, or anything that changes what the graph shows at a step." --- # Graph replay @@ -19,24 +19,24 @@ entry panel. | File | Role | | --- | --- | -| `srcjs/updateGraph/atTick.js` | `atTick()` (also exported as `updateGraph`): sets `rlog.curTick`, calls `displayAtStep()`, updates progress bar and log entry | -| `srcjs/graph/GraphAtStep.js` | `GraphAtStep`: step indexes, `rawGraphAtStep()`, hover/sticky/filter state, `displayAtStep()` | -| `srcjs/graph/Graph.js` | `Graph`: `nodes`, `edges`, `edgesUnique`, `addEntry()`, family-tree queries, `filterGraphOnNodeIds()`, `cytoGraph` | -| `srcjs/graph/Node.js` | `Node`: statuses, labels, `cytoClasses` | -| `srcjs/graph/Edge.js`, `GhostEdge.js` | Live dependency edges and "ever existed" edges | -| `srcjs/graph/StatusArr.js` | Per-node stack of open enter/invalidate/isolate entries, `expectPrevStatus()` | -| `srcjs/graph/ActiveStateStatus.js` | `off` / `on` / `finished` state plus `activeStep` | -| `srcjs/graph/HoverStatus.js` | `state` (focused / notFocused), `sticky`, `selected`, `filtered` flags | -| `srcjs/updateGraph/hoverStickyFilterSearch.js`, `searchString.js` | Entry points that set hover/sticky/filter/search, then call `updateGraph()` | -| `srcjs/cyto/cytoOn.js` | Mouse events: hover, click (sticky), double click (filter) | -| `srcjs/cyto/cytoscapeInit.js` | Creates cytoscape with the ordered style list | -| `srcjs/cyto/cytoClasses.js`, `cytoStyle.js`, `srcjs/style/colors.js` | Class selectors, style objects, color values | -| `srcjs/cyto/layoutOptions.js` | dagre layout options | +| `srcts/updateGraph/atTick.ts` | `atTick()` (also exported as `updateGraph`): sets `rlog.curTick`, calls `displayAtStep()`, updates progress bar and log entry | +| `srcts/graph/GraphAtStep.ts` | `GraphAtStep`: step indexes, `rawGraphAtStep()`, hover/sticky/filter state, `displayAtStep()` | +| `srcts/graph/Graph.ts` | `Graph`: `nodes`, `edges`, `edgesUnique`, `addEntry()`, family-tree queries, `filterGraphOnNodeIds()`, `cytoGraph` | +| `srcts/graph/Node.ts` | `Node`: statuses, labels, `cytoClasses` | +| `srcts/graph/Edge.ts`, `GhostEdge.ts` | Live dependency edges and "ever existed" edges | +| `srcts/graph/StatusArr.ts` | Per-node stack of open enter/invalidate/isolate entries, `expectPrevStatus()` | +| `srcts/graph/ActiveStateStatus.ts` | `off` / `on` / `finished` state plus `activeStep` | +| `srcts/graph/HoverStatus.ts` | `state` (focused / notFocused), `sticky`, `selected`, `filtered` flags | +| `srcts/updateGraph/hoverStickyFilterSearch.ts`, `searchString.ts` | Entry points that set hover/sticky/filter/search, then call `updateGraph()` | +| `srcts/cyto/cytoOn.ts` | Mouse events: hover, click (sticky), double click (filter) | +| `srcts/cyto/cytoscapeInit.ts` | Creates cytoscape with the ordered style list | +| `srcts/cyto/cytoClasses.ts`, `cytoStyle.ts`, `srcts/style/colors.ts` | Class selectors, style objects, color values | +| `srcts/cyto/layoutOptions.ts` | dagre layout options | ## Data flow ``` -navigation / hover / search -> atTick(k, cytoOptions) (updateGraph/atTick.js) +navigation / hover / search -> atTick(k, cytoOptions) (updateGraph/atTick.ts) rlog.curTick = k rlog.getGraph.displayAtStep(k, rlog.cyto, cytoOptions) fullFilteredGraphAtStep(k) @@ -116,10 +116,10 @@ observers and observables when `rlog.displayTimeOnNodes` is true ## Status to class to color `Node.cytoClasses` builds the class string. Styles are applied in the order -listed in `cytoscapeInit.js`, so later entries win (for example `nodeFrozen` +listed in `cytoscapeInit.ts`, so later entries win (for example `nodeFrozen` and `nodeHidden` override the fill colors). -| Class | When | Look (`cytoStyle.js`, `colors.js`) | +| Class | When | Look (`cytoStyle.ts`, `colors.ts`) | | --- | --- | --- | | (default) | every node | fill `colors.nodes.ready` `#a3c586` (green) | | `nodeStart` / `nodeMiddle` / `nodeEnd` | type is not observer/observable / `observable` / `observer` | polygon shape | @@ -168,7 +168,7 @@ Then for each remaining entry: `depOnReactId` for edges) is in `finalFilteredGraph`, keeps `userMark`s, and keeps an `idle` only if the last kept step is not an `idle`. `nextStep()` / `prevStep()` move through `filteredStepsVisible`. Steps equal log indexes -(`log/initStep.js`), so `this.log[step]` is valid. +(`log/initStep.ts`), so `this.log[step]` is valid. ## Hover, sticky, filter, search @@ -179,7 +179,7 @@ only by `updateFilterDatas()`). In `filteredGraphAtStep(k)`: -1. **Hover** (mouseover after 200 ms in `cytoOn.js`): if the hovered item +1. **Hover** (mouseover after 200 ms in `cytoOn.ts`): if the hovered item exists at step `k`, its family tree from `finalFilteredGraph` is set focused and everything else notFocused; the item itself gets `selected`. 2. **Sticky** (single click; click on background clears): if any sticky item @@ -191,7 +191,7 @@ In `filteredGraphAtStep(k)`: Family trees come from the whole-log graph, so hovering highlights nodes that are related at any time, not only at step `k`. -**Search** (`searchString.js`): an empty string resets everything; 1-2 +**Search** (`searchString.ts`): an empty string resets everything; 1-2 characters are ignored unless they look like `r`; otherwise the text is escaped into a `RegExp` and `updateSearchRegex()` tests it against node `label` and `key` in `finalCompleteGraph`, then edge `ghostKey`, then ghost @@ -256,7 +256,7 @@ layout. `resize()` calls `atTick(curTick, {fit: true, forceRedraw: true})`. - **Copied nodes share `ActiveStateStatus`.** `new Graph(prior)` clones `statusArr` and `hoverStatus`, but the three `ActiveStateStatus` objects are shared, so mutating them on the copy also changes the source. -- **Style order matters.** A new class needs `cytoClasses.js`, `cytoStyle.js`, - and a line at the right position in `cytoscapeInit.js`. +- **Style order matters.** A new class needs `cytoClasses.ts`, `cytoStyle.ts`, + and a line at the right position in `cytoscapeInit.ts`. - **Search matches are substrings.** The regex is not anchored, so searching `r1` also matches `r10`, `r12`, and labels containing `r1`. diff --git a/memory-bank/jsBuild.md b/memory-bank/jsBuild.md index b7b1641..087bd7f 100644 --- a/memory-bank/jsBuild.md +++ b/memory-bank/jsBuild.md @@ -1,105 +1,116 @@ --- title: "JavaScript build" -description: "The npm/grunt/webpack 4 toolchain that turns srcjs/ into the committed bundle inst/reactlog/reactlogAsset/reactlog.js, plus Babel, Flow, eslint, prettier, the pre-commit hook, the license scripts, and which CI job runs Node. Read before changing srcjs/, package.json, Gruntfile.js, or any JS config file." +description: "The npm/esbuild/TypeScript toolchain that turns srcts/ into the committed bundle inst/reactlog/reactlogAsset/reactlog.js, plus tsconfig, eslint, prettier, the pre-commit hook, the license scripts, and which CI job runs Node. Read before changing srcts/, package.json, tsconfig.json, or any JS config file." --- # JavaScript build -The viewer is written in Flow-typed ES6 under `srcjs/`. Grunt runs webpack 4, -which runs Babel, and writes one minified file plus a source map into -`inst/reactlog/reactlogAsset/`. Both files are committed, because installing -the R package never runs Node. For how the bundle is used at runtime, see +The viewer is written in TypeScript under `srcts/`. esbuild bundles it into one +minified file plus a source map in `inst/reactlog/reactlogAsset/`, and `tsc` +checks the types. Both output files are committed, because installing the R +package never runs Node. For how the bundle is used at runtime, see `memory-bank/architecture.md`. For the short command list, see `AGENTS.md`. -The repo moved from yarn to npm (`package-lock.json`; `yarn.lock` is gone). At -the same time, `flow-typed install` was dropped from the `postinstall` step; -the stubs it made are now committed in `flow-typed/`. +The folder is named `srcts/` to match `rstudio/shiny`. The shared `routine` +CI job knows both `srcjs/` and `srcts/`. + +## History + +Until 2026 the code was Flow-typed JavaScript in `srcjs/`, built by grunt, +webpack 4, and Babel, with `flow-bin` 0.86, eslint 5, and prettier 1. It was +converted by hand-editing the Flow syntax in place (renames keep the git +history): casts `(x: T)` became `x as T`, `?T` became `T | null | undefined`, +`%checks` became a type guard, Flow's `Object` became `any`, `$Values` and +`$ReadOnlyArray` became their TypeScript forms, and the `states` object in +`log/logStates.ts` became `as const` so `switch (entry.action)` narrows. +`flow-to-ts` was tried and dropped because it rewrote formatting and removed +blank lines. ## Key files | File | Role | | --- | --- | -| `package.json` | npm scripts, `pre-commit` / `lint-staged` hook config, `flow-coverage-report` config, dependencies | -| `Gruntfile.js` | The webpack config (`gruntConfig.webpack.reactlog`) and the `webpackSetWatch` task | -| `.babelrc` | Babel presets: Flow stripping, `preset-env`, class properties | -| `.flowconfig` | Flow settings; only `srcjs` is included | -| `.eslintrc.js` / `prettier.config.js` | Lint and format rules | +| `package.json` | npm scripts, `pre-commit` / `lint-staged` hook config, dependencies | +| `tsconfig.json` | `tsc` settings: `noEmit`, `target` es2021, `moduleResolution` bundler, `strict` off | +| `srcts/modules.d.ts` | `declare module` for untyped `cytoscape` / `cytoscape-dagre`, and the `window` globals | +| `srcts/cyto/cytoFlowType.ts` | Hand-written types for the parts of cytoscape the app uses | +| `eslint.config.mjs` / `prettier.config.js` | Lint and format rules | | `bin/lint_file.sh` | `eslint --fix` then `prettier --write` on the given files | -| `bin/clean_rlog_file.sh` | Lint the given files, then `npm run build-only` | +| `bin/clean_rlog_file.sh` | Lint the given files, `typecheck`, then `build-only` | | `bin/postinstall.sh` | Runs `npm run build-only` | +| `bin/check_shiny_target.js` | Fails if our tsc / esbuild target differs from rstudio/shiny's | | `bin/update-license.js` | Prints a third-party license list (not used today, see below) | | `inst/reactlog/reactlogAsset/reactlog.js` (+ `.map`) | The committed build output | | `inst/reactlog/reactlog.html`, `inst/reactlog/defaultLog.js` | Page and sample log for the local dev loop | ## npm scripts -- `build-only`: `NODE_OPTIONS=--openssl-legacy-provider grunt`. Runs the grunt - `default` task, which is `webpack:reactlog`. -- `build`: `lint-all` then `build-only`. `lint-all` runs `bin/lint_file.sh` on - `srcjs/*.js srcjs/**/*.js`. All `srcjs/` sub folders are one level deep, so - this glob covers every file even without bash `globstar`. -- `watch`: `lint-all` once, then `grunt webpackSetWatch default`. - `webpackSetWatch` sets `gruntConfig.webpack.reactlog.watch = true` before the - webpack task runs, so webpack keeps running and rebuilds on save. Files are - not linted again while watching. +- `build-only`: `esbuild srcts/index.ts --bundle --minify --sourcemap + --target=es2021 --outfile=inst/reactlog/reactlogAsset/reactlog.js`. Takes + well under a second. esbuild strips types without checking them. +- `typecheck`: `tsc -p .`. Writes nothing. +- `build`: `lint-all`, then `typecheck`, then `build-only`. `lint-all` runs + `bin/lint_file.sh` on the `srcts` folder. +- `watch`: `lint-all` once, then `build-only -- --watch`. No type check while + watching; run `npm run typecheck` (or an editor) for that. - `lint` / `fix`: `bin/lint_file.sh` / `bin/clean_rlog_file.sh` on files you - pass after `--`. `lint` with no files exits with an error. -- `fix-all`: `fix` on every `srcjs` file (lint + build). -- `flow-report`, `flow-status`, `flow-stop`: Flow coverage and server commands. + pass after `--` (files or folders). `lint` with no arguments exits with an + error. +- `fix-all`: `fix` on every `srcts` file. +- `check-shiny-target`: `bin/check_shiny_target.js` (see below). Needs + network access. - `postinstall`: `bin/postinstall.sh`, so `npm install` / `npm ci` also builds. - `update-license`, `lint-staged`, `git_add_compiled`: used by the license script and the commit hook below. -## Why `--openssl-legacy-provider` - -Webpack 4 hashes with MD4, which OpenSSL 3 (Node 17+) no longer offers by -default. Without the flag, `grunt` on Node 22 fails at the end of the build -with `Fatal error: error:0308010C:digital envelope routines::unsupported`. Only -`build-only` and `watch` set the flag; call those scripts instead of running -`grunt` by hand. - -## Gruntfile.js (webpack config) - -- `entry`: `srcjs/index.js`. It starts with `import "@babel/polyfill"`. -- `output`: `inst/reactlog/reactlogAsset/reactlog.js`. -- `mode: "production"`: the output is minified by Terser, even though the - comment next to it says "do not take time to shrink files". -- `devtool: "source-map"`: writes `reactlog.js.map` next to the bundle and adds - a `//# sourceMappingURL=reactlog.js.map` line at the end. -- One rule: `babel-loader` for `.js` files outside `node_modules`. -- `failOnError: true`, `progress: true`. The progress output is long; read the - end of it. -- `BundleAnalyzerPlugin` and `DuplicatePackageCheckerPlugin` are imported but - commented out in `plugins`. Turning on the analyzer in `static` mode writes - `report.html`, which `.gitignore` lists as - `inst/reactlog/reactlogAsset/report.html`. - -## Babel, Flow, eslint, prettier - -- `.babelrc`: `@babel/preset-flow` strips types. `@babel/preset-env` uses - `modules: "umd"`, `useBuiltIns: "entry"` and `corejs: 2`, which swaps the - `@babel/polyfill` import for the needed `core-js` 2 parts. - `@babel/plugin-proposal-class-properties` allows class fields. -- `.flowconfig`: `[include] srcjs`, all lints set to `error` with a few turned - down, `$Flow...` comments suppress errors. `[libs]` is empty. Type stubs for - `jquery`, `lodash` and `@babel/polyfill` are committed in `flow-typed/npm/` - (from `npx flow-typed install --skip true --ignoreDeps dev`; it prints - "Cannot call write after a stream was destroyed" on new Node but still writes - the files). -- Flow coverage: `npm run flow-report` runs `flow-coverage-report` with the - `flow-coverage-report` block in `package.json`: files `srcjs/**/*.js`, four - files excluded (`utils/Console.js`, `cyto/layoutOptions.js`, - `log/initStep.js`, `rlog.js`), threshold 95%, text output. -- `flow-bin` is pinned to `^0.86.0`. Its macOS binary is x86_64 only. On Apple - Silicon without Rosetta, `flow-report` fails right away with - `spawn Unknown system error -86`. Newer `flow-bin` releases have an arm64 - binary, but report hundreds of errors on `srcjs/`, so that upgrade is its - own project. -- `.eslintrc.js`: `babel-eslint` parser, `flowtype` plugin, extends - `plugin:flowtype/recommended` and `eslint:recommended`. Notable rules: - `no-console` and `no-var` are errors, `eqeqeq`, `semi`, `guard-for-in`. +## TypeScript + +- `typescript` is pinned to `~6.0`. TypeScript 7 (the Go port) does not offer + the JS API that `typescript-eslint` needs, and `typescript-eslint` 8 lists + `typescript <6.1` as its peer. +- `strict` is off. `npx tsc -p . --strict` reports about 40 errors, mostly + missing null checks, implicit `any`, and class fields that are set outside + the constructor. Fix those in their own change. +- cytoscape 3.15 (the locked version) has no bundled types, so it is declared + as an untyped module and the app uses the hand-written types in + `cyto/cytoFlowType.ts`. Newer cytoscape ships `index.d.ts`, but upgrading it + changes the runtime. +- jQuery and lodash use `@types/jquery` (v3, to match jQuery 3) and + `@types/lodash`. +- No polyfills, like rstudio/shiny. The old build added `@babel/polyfill` / + core-js 2. + +## Matching rstudio/shiny's target + +The tsc `target` (and `lib`) in `tsconfig.json` and esbuild's `--target` in the +`build-only` script match rstudio/shiny: es2021 in shiny's `tsconfig.json` and +`srcts/build/_build.ts`. The viewer only opens from a Shiny app, so its browser +already runs Shiny's code; an older target gains nothing. + +`npm run check-shiny-target` fetches those two shiny files from GitHub (`main`, +or pass a ref: `node bin/check_shiny_target.js v1.11.0`), prints all four +targets, and exits 1 if any differ. `.github/workflows/shiny-target.yaml` runs +it on PRs that touch `tsconfig.json`, `package.json`, the script, or the +workflow, every Monday, and on demand. When it fails because shiny moved, +update `target` and `lib` in `tsconfig.json` and `--target` in `build-only`, +run `npm run build`, and check the viewer in a browser. It does not compare +`lib`, so keep that in step by hand. + +## eslint and prettier + +- `eslint.config.mjs` (flat config, eslint's `defineConfig()`; the older + `tseslint.config()` helper is deprecated): `@eslint/js` recommended plus + `typescript-eslint` recommended (no type-aware rules). Kept from the old + config: `eqeqeq`, `guard-for-in`, `no-console` and `no-var` as errors, + `consistent-return` as a warning, unused vars as warnings with + `args: "none"`, `no-fallthrough` off. Turned off to match the existing code: + `no-explicit-any` (Flow's `Object` became `any`), `prefer-const` (the code + uses `let` everywhere), and `no-this-alias` (`let self = this`). + `no-unused-expressions` allows `a() || b()`. - `prettier.config.js`: 2 spaces, width 80, double quotes, semicolons, `trailingComma: "es5"`, `arrowParens: "avoid"`. +- The old formatting rules (`semi`, `comma-dangle`, `dot-location`) were + dropped; prettier handles them. ## The commit hook @@ -111,31 +122,27 @@ a time (`concurrent: false`), adding the staged file names to each command: - `R/*.R`: `Rscript` runs `devtools::load_all()` and `devtools::lint()` on the whole package and fails if any lint is found. Needs R with `devtools`, `lintr` and `testthat`. -- `.flowconfig`: `npm run flow-report-and-echo`, which runs - `bin/flow_report.sh`. That script skips the report with a warning when the - Flow binary cannot run (Apple Silicon without Rosetta). -- `srcjs/**/*.js`: `npm run fix --` (lint those files, then build), - `npm run flow-report-and-echo`, then `npm run git_add_compiled --`, which runs - `git add` on `inst/reactlog/reactlogAsset/reactlog.js` and its `.map`, then - `git add` on the files. +- `srcts/**/*.ts`: `npm run fix --` (lint those files, typecheck, build), then + `npm run git_add_compiled --`, which runs `git add` on + `inst/reactlog/reactlogAsset/reactlog.js` and its `.map`, then `git add` on + the files. -So a JS commit rebuilds and stages the bundle for you. Where Flow cannot run, -the Flow coverage check is skipped. +So a TS commit rebuilds and stages the bundle for you. ## Committed bundle and the R package `reactlog.js` and `reactlog.js.map` are tracked in git so that `install.packages()`, `R CMD check` and CRAN never need Node. Rebuild and commit -the bundle in the same change as any `srcjs/` edit. A clean `npm run build-only` +the bundle in the same change as any `srcts/` edit. A clean `npm run build-only` on unchanged sources gives a byte-identical bundle, so a diff in `inst/` after -building means the sources or the installed packages changed. +building means the sources or the installed packages changed. Changes that only +touch formatting or comments change the `.map` but not `reactlog.js`. -`.Rbuildignore` keeps the JS toolchain out of the R tarball: `^srcjs`, -`^node_modules`, `^bin`, `^flow-typed`, `^package\.json$`, -`^package-lock\.json$`, `^Gruntfile\.js$`, `^\.babelrc$`, `^\.eslintrc\.js$`, -`^\.flowconfig$`, `^prettier\.config\.js$`, `^inst/log-files`, and -`.*\.js\.map$`. The last one means the source map is in git but not in the R -package; the `sourceMappingURL` line still points to it. +`.Rbuildignore` keeps the JS toolchain out of the R tarball: `^srcts`, +`^node_modules`, `^bin`, `^package\.json$`, `^package-lock\.json$`, +`^tsconfig\.json$`, `^eslint\.config\.mjs$`, `^prettier\.config\.js$`, +`^inst/log-files`, and `.*\.js\.map$`. The last one means the source map is in +git but not in the R package; the `sourceMappingURL` line still points to it. ## CI and Node @@ -144,8 +151,9 @@ package; the `sourceMappingURL` line still points to it. `.github/workflows/R-CMD-check.yaml`) does: when `package.json` has a `build` script it runs `npm ci` (which builds through `postinstall`), then `npm run build` on the current Node release, and commits any changes under -`inst/` and `srcjs/` plus `package-lock.json`. On a same-repo PR it pushes that -commit back; on a push to `main` it fails instead. That job does not run Flow. +`inst/`, `srcjs/`, and `srcts/` plus `package-lock.json`. On a same-repo PR it +pushes that commit back; on a push to `main` it fails instead. A type error +fails that job. ## License list @@ -158,24 +166,35 @@ with their license text, under a header saying the package is MIT. ## Dev loop without Shiny 1. `npm run watch` (or `npm run build-only` after each change). -2. Open `inst/reactlog/reactlog.html` in a browser straight from disk. It loads - `defaultLog.js` (sets `window.log`) and then `reactlogAsset/reactlog.js`. +2. Open `inst/reactlog/reactlog.html` in a browser straight from disk, or serve + `inst/reactlog/` (`python3 -m http.server`). It loads `defaultLog.js` (sets + `window.log`) and then `reactlogAsset/reactlog.js`. 3. Refresh after each rebuild. To try another log, copy a file from `inst/log-files/` over `inst/reactlog/defaultLog.js` (and do not commit that unless you mean to; the website demo uses it). +To check that a build change did not change behavior, load the old and new +bundle in turn and compare `window._reactlog.curTick`, +`window._reactlog.cyto.nodes().length` and the `#logEntry` text after the same +button clicks and key presses. There are no JS unit tests. + ## Pitfalls -- Do not run `grunt` without `NODE_OPTIONS=--openssl-legacy-provider` on Node - 17+; use `npm run build-only`. -- On Apple Silicon without Rosetta, Flow cannot run. `npm run flow-report` - fails, and the commit hook skips the Flow check. Install Rosetta to get the - coverage check back. +- `npm run build-only` and `watch` do not type check. Run `npm run typecheck` + or `npm run build` before committing. +- Do not upgrade `typescript` past 6.0 until `typescript-eslint` supports it. +- Bind jQuery handlers through an arrow, as in + `.click(() => updateGraph.buttonNextStep())`. Passing the function itself + hands jQuery's event in as the first argument, which for most `updateGraph` + functions is `cytoOptions`, and that ends up in the cytoscape layout options. - `npm install` / `npm ci` rebuilds the bundle. If `inst/` changes afterwards, check whether you meant it; restore with `git checkout -- inst/`. +- `npm install` in a folder other than `reactlog` rewrites the `"name"` at the + top of `package-lock.json` (there is no `name` in `package.json`). Put it back + before committing. - The `R/*.R` hook lints the whole package, not just your staged files, and needs `lintr` installed. - `npm run build` runs `eslint --fix` and `prettier --write` on all of - `srcjs/`, so it can change source files, not just the bundle. CI does the same + `srcts/`, so it can change source files, not just the bundle. CI does the same and may push a commit to your PR. - New top-level JS files need an `.Rbuildignore` entry or `R CMD check` warns. diff --git a/memory-bank/logFormat.md b/memory-bank/logFormat.md index 49701b4..8a46499 100644 --- a/memory-bank/logFormat.md +++ b/memory-bank/logFormat.md @@ -20,11 +20,11 @@ Key files: | `R/version.R` | `reactlog_upgrade()`: checks the `version` attribute | | `R/showReactLog.R` | `reactlog_write()`: session filter and JSON | | `R/renderReactLog.R` | `reactlog_render()`: upgrade, write, put JSON into `reactlog.html` | -| `srcjs/log/logStates.js` | `LogStates` (every action name) and the Flow type of each entry | -| `srcjs/log/initStep.js` | Gives each entry `step = index` | -| `srcjs/graph/Graph.js` | `addEntry()`: how each action changes the graph | -| `srcjs/graph/GraphAtStep.js` | `initStepInfo()`, `updateFilteredStepsVisible()`: which actions are visible steps | -| `srcjs/layout/logEntry.js` | `statusForEntry()`: text shown for each action | +| `srcts/log/logStates.ts` | `LogStates` (every action name) and the TypeScript type of each entry | +| `srcts/log/initStep.ts` | Gives each entry `step = index` | +| `srcts/graph/Graph.ts` | `addEntry()`: how each action changes the graph | +| `srcts/graph/GraphAtStep.ts` | `initStepInfo()`, `updateFilteredStepsVisible()`: which actions are visible steps | +| `srcts/layout/logEntry.ts` | `statusForEntry()`: text shown for each action | | `inst/reactlog/defaultLog.js`, `inst/log-files/` | Sample logs | Read Shiny's R source on GitHub (the installed package only has compiled R @@ -103,7 +103,7 @@ Notes on fields: - `type` on `define` is one of `observable`, `observer`, `reactiveVal`, `reactiveValuesNames`, `reactiveValuesAsList`, `reactiveValuesAsListAll`, - `reactiveValuesKey` (see the commented `TypeType` in `logStates.js`). The JS + `reactiveValuesKey` (see the commented `TypeType` in `logStates.ts`). The JS only treats `observable` and `observer` as "middle/end" nodes. - `type` on contexts, `enter`, and `invalidateStart` is the context type (`observable`, `observer`, `other`, `isolate`...). `initStepInfo()` skips @@ -116,7 +116,7 @@ Notes on fields: `(exit.time - enter.time) * 1000` ms. `Node` and `Edge` constructors throw if `time` or `session` is `undefined` (but `null` is fine; it shows as `"Global"`). -- The Flow types in `logStates.js` are not exact: `enter`/`exit` entries carry +- The types in `logStates.ts` are not exact: `enter`/`exit` entries carry `type` but `LogEntryEnterType`/`LogEntryExitType` do not list it. ## Version and `reactlog_upgrade()` @@ -149,9 +149,9 @@ sees the version (JSON drops R attributes). ## How the browser assigns `step` -`srcjs/log/initStep.js` runs when imported. If `window.__APP_DATA__` exists it +`srcts/log/initStep.ts` runs when imported. If `window.__APP_DATA__` exists it replaces `window.log`. Then it sets `entry.step = i` for every entry. -`index.js` then sets `rlog.log = window.log`. +`index.ts` then sets `rlog.log = window.log`. Because of this, `step` always equals the array index of the log that was loaded (after session filtering). `GraphAtStep` depends on that: it reads @@ -182,20 +182,20 @@ In shiny (`R/graph.R` and the call site): 3. If old reactlog versions cannot handle it, raise `reactlog_min_version` in `R/graph.R` and the reactlog version in shiny's DESCRIPTION. -In reactlog (`srcjs/`), then rebuild the bundle (`npm run build`) and commit +In reactlog (`srcts/`), then rebuild the bundle (`npm run build`) and commit `inst/reactlog/reactlogAsset/`: -1. `log/logStates.js`: add the name to `states`, add a `LogEntry...Type`, add +1. `log/logStates.ts`: add the name to `states`, add a `LogEntry...Type`, add it to `LogEntryAnyType` (and `LogEntryHasReactId` if it has `reactId`), and export it. -2. `graph/Graph.js` `addEntry()`: add a `case` (even a no-op). Unknown actions +2. `graph/Graph.ts` `addEntry()`: add a `case` (even a no-op). Unknown actions hit `default` and log `console.error("unknown data.action: ", ...)`. -3. `graph/GraphAtStep.js` `initStepInfo()`: the second `switch` makes every +3. `graph/GraphAtStep.ts` `initStepInfo()`: the second `switch` makes every action a visible step by `default`. Add it to the "skip" list if it should not be a stop for next/prev. -4. `graph/GraphAtStep.js` `updateFilteredStepsVisible()`: add a `case`. Its +4. `graph/GraphAtStep.ts` `updateFilteredStepsVisible()`: add a `case`. Its `default` throws `"unknown logEntry action in 'next'"` when a filter is on. -5. `layout/logEntry.js` `statusForEntry()`: add display text. Its `default` +5. `layout/logEntry.ts` `statusForEntry()`: add display text. Its `default` throws `not implemented for log status`. 6. Add a sample log with the new action to `inst/log-files/` if useful. @@ -205,9 +205,9 @@ In reactlog (`srcjs/`), then rebuild the bundle (`npm run build`) and commit `Graph.addEntry()` ignores it. Shiny passes the raw `ctx$id` without the `"ctx"` prefix, so do not match its `ctxId` against other entries. - **`updateNodeLabel` has no producer.** Shiny does not write it today; the JS - handlers are left over. The comment in `Graph.js` shows an old `nodeId` field. + handlers are left over. The comment in `Graph.ts` shows an old `nodeId` field. - **`step` is the index, not a saved field.** Do not filter or reorder - `rlog.log` after `initStep.js` runs, or `this.log[step]` lookups break. + `rlog.log` after `initStep.ts` runs, or `this.log[step]` lookups break. - **Session filtering keeps all global entries.** Entries with `session: null` (every `idle`, and anything made outside a session) stay in every per-session log. diff --git a/memory-bank/shinyIntegration.md b/memory-bank/shinyIntegration.md index 4567712..13e0f89 100644 --- a/memory-bank/shinyIntegration.md +++ b/memory-bank/shinyIntegration.md @@ -149,9 +149,9 @@ If `getOption("shiny.testmode")` is `TRUE` when the iframe renders, an app runs under shinytest or shinytest2 (`test.mode` in `rstudio/shiny:R/runapp.R`). -On the JS side, `srcjs/utils/shinytest.js` sets `inTestMode` when the page's -query has `test=1`. Then `srcjs/layout/logEntry.js` shows `(shinytest)` instead -of the time and session, and `Node.calculationTimeStr` in `srcjs/graph/Node.js` +On the JS side, `srcts/utils/shinytest.ts` sets `inTestMode` when the page's +query has `test=1`. Then `srcts/layout/logEntry.ts` shows `(shinytest)` instead +of the time and session, and `Node.calculationTimeStr` in `srcts/graph/Node.ts` returns `(shinytest)` instead of milliseconds. This keeps screenshots stable between runs. @@ -208,10 +208,10 @@ Run with `Rscript -e 'devtools::test()'` from the repo root. | Change | reactlog | shiny | | --- | --- | --- | -| New or changed log action or field | Update `srcjs/` (see [logFormat.md](logFormat.md)), rebuild and commit the bundle | Update `RLog` in `rstudio/shiny:R/graph.R` | +| New or changed log action or field | Update `srcts/` (see [logFormat.md](logFormat.md)), rebuild and commit the bundle | Update `RLog` in `rstudio/shiny:R/graph.R` | | Breaking log format change | Add the new version to `reactlog_upgrade()`; teach the JS to read it | Change `attr(, "version")` in `RLog$asList()`; raise `reactlog_min_version` and `Suggests` | | Rename or change args of `reactlog_render()` / `reactlog_show()` | Keep old names working; release first | Update `reactlogShow()` and `renderReactlog()`; raise `reactlog_min_version` | -| New query parameter on `/reactlog` | Read it in `srcjs/` (like `utils/shinytest.js`) or in `reactlog_render()` | Read it in `reactLogHandler()` and pass it through `renderReactlog()` | +| New query parameter on `/reactlog` | Read it in `srcts/` (like `utils/shinytest.ts`) or in `reactlog_render()` | Read it in `reactLogHandler()` and pass it through `renderReactlog()` | | New keyboard shortcut | Update the docs in `R/showReactLog.R` and `R/shinyModule.R` | Add it in `rstudio/shiny:srcts/src/shiny/reactlog.ts` | | Module needs a newer shiny | Raise `shiny (>= ...)` in DESCRIPTION `Suggests` | Nothing | | Template placeholders | Update `reactlog.html` and `reactlog_render()` together | Nothing | diff --git a/memory-bank/viewerUi.md b/memory-bank/viewerUi.md index e4a4129..ef860f0 100644 --- a/memory-bank/viewerUi.md +++ b/memory-bank/viewerUi.md @@ -1,30 +1,30 @@ --- title: "Viewer UI and navigation" -description: "The reactlog viewer page: DOM ids in reactlog.html, how srcjs/index.js wires them up, the navigation actions in srcjs/updateGraph/, every keyboard shortcut, the timeline, the status panel, mouse events on the graph, the test=1 query param, and the CSS/images. Read before changing buttons, shortcuts, the header, the timeline, or mouse behavior." +description: "The reactlog viewer page: DOM ids in reactlog.html, how srcts/index.ts wires them up, the navigation actions in srcts/updateGraph/, every keyboard shortcut, the timeline, the status panel, mouse events on the graph, the test=1 query param, and the CSS/images. Read before changing buttons, shortcuts, the header, the timeline, or mouse behavior." --- # Viewer UI and navigation -The viewer is one HTML page (`inst/reactlog/reactlog.html`) plus the bundle built from `srcjs/`. This doc covers what the user sees and how input (buttons, keys, mouse, timeline) turns into a new step on screen. For the overall data flow see [architecture.md](architecture.md). For how a step becomes a graph (statuses, filtering, cytoscape diff) see [graphReplay.md](graphReplay.md). +The viewer is one HTML page (`inst/reactlog/reactlog.html`) plus the bundle built from `srcts/`. This doc covers what the user sees and how input (buttons, keys, mouse, timeline) turns into a new step on screen. For the overall data flow see [architecture.md](architecture.md). For how a step becomes a graph (statuses, filtering, cytoscape diff) see [graphReplay.md](graphReplay.md). Key files: | File | Role | | --- | --- | | `inst/reactlog/reactlog.html` | Page template: placeholders, header, timeline, graph, legend, hidden log panel | -| `srcjs/index.js` | On page load: builds `GraphAtStep`, binds buttons, legend colors, timeline ticks, resize, search, keys, first step | -| `srcjs/rlog.js` | Shared state: `curTick`, `getGraph`, `cyto`, `displayTimeOnNodes` | -| `srcjs/updateGraph/index.js` | Re-exports all navigation actions; `atTick` is also exported as `updateGraph` | -| `srcjs/updateGraph/atTick.js` | The one function every action ends in | -| `srcjs/updateGraph/step.js`, `tick.js`, `idle.js`, `outputCalc.js`, `userMarks.js` | Move to a step | -| `srcjs/updateGraph/buttons.js` | `button*` wrappers: try a move, else go to first/last step | -| `srcjs/updateGraph/searchString.js`, `hoverStickyFilterSearch.js` | Search box, hover, sticky (click), filter (double click) | -| `srcjs/updateGraph/resize.js` | Resize cytoscape and refit | -| `srcjs/layout/keydown.js` | All keyboard shortcuts (`onKeydown`) | -| `srcjs/layout/progressBar.js` | Timeline fill, ticks, click/drag to seek | -| `srcjs/layout/logEntry.js` | Step / time / session / status text and the raw JSON panel | -| `srcjs/cyto/cytoOn.js` | Mouse events on the graph (hover, tap, double click) | -| `srcjs/utils/queryString.js`, `srcjs/utils/shinytest.js` | Read `?test=1` | +| `srcts/index.ts` | On page load: builds `GraphAtStep`, binds buttons, legend colors, timeline ticks, resize, search, keys, first step | +| `srcts/rlog.ts` | Shared state: `curTick`, `getGraph`, `cyto`, `displayTimeOnNodes` | +| `srcts/updateGraph/index.ts` | Re-exports all navigation actions; `atTick` is also exported as `updateGraph` | +| `srcts/updateGraph/atTick.ts` | The one function every action ends in | +| `srcts/updateGraph/step.ts`, `tick.ts`, `idle.ts`, `outputCalc.ts`, `userMarks.ts` | Move to a step | +| `srcts/updateGraph/buttons.ts` | `button*` wrappers: try a move, else go to first/last step | +| `srcts/updateGraph/searchString.ts`, `hoverStickyFilterSearch.ts` | Search box, hover, sticky (click), filter (double click) | +| `srcts/updateGraph/resize.ts` | Resize cytoscape and refit | +| `srcts/layout/keydown.ts` | All keyboard shortcuts (`onKeydown`) | +| `srcts/layout/progressBar.ts` | Timeline fill, ticks, click/drag to seek | +| `srcts/layout/logEntry.ts` | Step / time / session / status text and the raw JSON panel | +| `srcts/cyto/cytoOn.ts` | Mouse events on the graph (hover, tap, double click) | +| `srcts/utils/queryString.ts`, `srcts/utils/shinytest.ts` | Read `?test=1` | | `inst/reactlog/reactlogAsset/` | `reactlog.css`, font CSS, `images/*.svg`, built `reactlog.js` | ## The HTML template @@ -34,23 +34,23 @@ Key files: - `window.__APP_DATA__ = __DATA__;` R replaces `__DATA__` with the log JSON. - `window.__APP_TIME__ = String(__TIME__).toLowerCase() === "true";` R replaces `__TIME__` with `"TRUE"` or `"FALSE"` (quoted, from `reactlog_render(time =)` in `R/renderReactLog.R`). -When you open the raw template in a browser, `__DATA__` is an unknown name, the `try` swallows the error, and both globals stay unset. Then `log/initStep.js` uses `window.log` from `defaultLog.js`, and time display stays on. R also removes the `