mbeditor 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +157 -1
  3. data/app/assets/javascripts/mbeditor/application.js +3 -1
  4. data/app/assets/javascripts/mbeditor/audit_log.js +165 -0
  5. data/app/assets/javascripts/mbeditor/collaboration_service.js +264 -22
  6. data/app/assets/javascripts/mbeditor/components/ChangelogView.js +89 -94
  7. data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +6 -9
  8. data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +12 -7
  9. data/app/assets/javascripts/mbeditor/components/CombinedDiffViewer.js +20 -0
  10. data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
  11. data/app/assets/javascripts/mbeditor/components/EditorPanel.js +440 -334
  12. data/app/assets/javascripts/mbeditor/components/FileHistoryPanel.js +6 -9
  13. data/app/assets/javascripts/mbeditor/components/FileTree.js +26 -12
  14. data/app/assets/javascripts/mbeditor/components/GitPanel.js +3 -0
  15. data/app/assets/javascripts/mbeditor/components/Gutter.js +51 -0
  16. data/app/assets/javascripts/mbeditor/components/ImportDialog.js +9 -1
  17. data/app/assets/javascripts/mbeditor/components/LogPanel.js +3 -44
  18. data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +1322 -1431
  19. data/app/assets/javascripts/mbeditor/components/ModelGraph.js +94 -37
  20. data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +82 -72
  21. data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +121 -81
  22. data/app/assets/javascripts/mbeditor/components/SettingsModal.js +342 -0
  23. data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +2 -1
  24. data/app/assets/javascripts/mbeditor/components/TabBar.js +67 -98
  25. data/app/assets/javascripts/mbeditor/editor_plugins.js +694 -306
  26. data/app/assets/javascripts/mbeditor/file_import.js +13 -15
  27. data/app/assets/javascripts/mbeditor/file_service.js +46 -57
  28. data/app/assets/javascripts/mbeditor/git_service.js +15 -1
  29. data/app/assets/javascripts/mbeditor/history_service.js +5 -13
  30. data/app/assets/javascripts/mbeditor/search_service.js +29 -2
  31. data/app/assets/javascripts/mbeditor/tab_manager.js +135 -54
  32. data/app/assets/javascripts/mbeditor/websocket_service.js +13 -5
  33. data/app/assets/stylesheets/mbeditor/application.css +6 -1
  34. data/app/assets/stylesheets/mbeditor/editor.css +762 -297
  35. data/app/assets/stylesheets/mbeditor/glass.css +163 -0
  36. data/app/assets/stylesheets/mbeditor/themes.css +90 -30
  37. data/app/channels/mbeditor/collaboration_channel.rb +25 -6
  38. data/app/controllers/mbeditor/application_controller.rb +26 -3
  39. data/app/controllers/mbeditor/editors_controller.rb +125 -465
  40. data/app/services/mbeditor/archive_service.rb +137 -0
  41. data/app/services/mbeditor/collaboration_doc_store.rb +180 -12
  42. data/app/services/mbeditor/duplicate_content_scanner.rb +105 -0
  43. data/app/services/mbeditor/editor_state_service.rb +14 -51
  44. data/app/services/mbeditor/file_history_service.rb +222 -0
  45. data/app/services/mbeditor/git_info_service.rb +6 -0
  46. data/app/services/mbeditor/js_globals_service.rb +12 -1
  47. data/app/services/mbeditor/js_syntax_check_service.rb +42 -16
  48. data/app/services/mbeditor/lint_service.rb +137 -0
  49. data/app/services/mbeditor/locked_json_file.rb +67 -0
  50. data/app/services/mbeditor/process_runner.rb +32 -0
  51. data/app/services/mbeditor/rubocop_run_service.rb +17 -5
  52. data/app/services/mbeditor/ruby_lsp_result_translator.rb +226 -0
  53. data/app/services/mbeditor/schema_service.rb +8 -2
  54. data/app/services/mbeditor/search_replace_service.rb +19 -2
  55. data/app/services/mbeditor/test_runner_service.rb +3 -56
  56. data/app/views/layouts/mbeditor/application.html.erb +1 -1
  57. data/lib/mbeditor/audit_log.rb +203 -0
  58. data/lib/mbeditor/configuration.rb +6 -9
  59. data/lib/mbeditor/rack/pending_migration_bypass.rb +15 -9
  60. data/lib/mbeditor/route_map.rb +4 -1
  61. data/lib/mbeditor/ruby_lsp_client.rb +82 -14
  62. data/lib/mbeditor/version.rb +1 -1
  63. data/lib/mbeditor.rb +1 -0
  64. data/lib/tasks/mbeditor.rake +23 -0
  65. metadata +14 -3
  66. data/app/assets/javascripts/mbeditor/components/TestRunPanel.js +0 -312
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b65424322b66f78929de0629db6e4644a76467e889adaee77121b8c52e0e50de
4
- data.tar.gz: 53cd7724f2a80011d474a9616bdb35d88859ddf797a0fa51d3657740038624bd
3
+ metadata.gz: 89b467923bfe67c35d68e57b667357c96c4217c39fff779f5ff98911eed59f25
4
+ data.tar.gz: e1b82cc6eddeaec6d31f00f83eff3b3e56abba423411304c9025b68dd9efbd81
5
5
  SHA512:
6
- metadata.gz: f4470580292e46d6de7eb2e3687193452bfbcbee5d478e48c6460e22739fd2942d1a525b04e6d3f7877175db5d18da86c3241032dfc748ca91bf62f0f008cd90
7
- data.tar.gz: 05e4226ce7151a7e72d18f34018c30acc04adfbaea235a891afe12ecadb52170b1b598820ea7de326068eae7bb7811cce13ec5cf42b0c69fd6c77683c3b7c819
6
+ metadata.gz: 1e15d0215eba919681e9a435de8aeadcd3d24bbc1c9dc72f95f9036c2a39a89e39b28461d2acce844b4a9b5c2b1507318d84da3cdae62b08162f6412daa4dd8c
7
+ data.tar.gz: 611f9efe9f06536eb81ecf2b8f09d2da282958a17131b56baca6b47c07d10c2508a7797cba2f5469df457c2647b052a26f97a281fa8f44d7e574a2d89fe12d83
data/CHANGELOG.md CHANGED
@@ -5,7 +5,163 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [0.14.0] - 2026-09-21
9
+
10
+ ### Added
11
+ - **Liquid Glass chrome, opt-in** (Settings → Appearance). Translucent, blurred
12
+ panels layered over whichever theme is active — it is a layer, not a twelfth
13
+ theme, so every colour is derived from the existing `--ide-*` tokens. Monaco
14
+ itself stays solid: legibility beats the effect, and a blurred backdrop under
15
+ a text editor costs a re-composite per repaint. The default chrome is the new
16
+ Modern one, matching VS Code's Dark Modern.
17
+ - **Drag-to-open gutters.** One `Gutter` component owns every draggable gap.
18
+ Drag the strip above the status bar to open Problems, the right edge to open
19
+ the git panel, and the collapsed sidebar's gutter rightwards to snap the
20
+ explorer back open.
21
+ - **Settings is a searchable modal**, not an editor tab, so it no longer takes a
22
+ pane or persists into your tab list.
23
+ - **The model graph opens as its own view**, not a tab: the activity-bar icon
24
+ toggles it, it takes the whole centre column, and both side panels hide with
25
+ it. A layered graph is inherently wide and the tab gave it whatever was left
26
+ beside the explorer.
27
+ - **A downloadable audit log** (Settings → Diagnostics) that structurally cannot
28
+ carry a file name, path or URL — every field is a number, boolean or authored
29
+ Symbol.
30
+ - **Quick Open as a frosted palette** over the title bar, with a chevron
31
+ breadcrumb and VS Code-style Open Editors rows.
32
+ - Auto-close tags in ERB and multi-line JSX, linked tag pairs, and `props.x`
33
+ completion.
34
+ - Multi-select download in the explorer.
35
+ - A `toolbarLabels` preference: toolbar button labels are now opt-in.
36
+ - Accessibility: a focus-visible ring throughout, a real tab-close button, a
37
+ higher-contrast version chip, and the offline pulse respects reduced-motion.
38
+
39
+ ### Changed
40
+ - **The pending-migration warning is no longer a banner.** It sat above
41
+ everything and pushed the editor down the page every time it appeared. The
42
+ status bar turns amber instead and carries a "Migrations pending" item. Both
43
+ halves of that colour are per-theme, because `--ide-warning` is authored as a
44
+ foreground colour and several themes pick an ochre that is unreadable as a
45
+ background; measured contrast is 4.87:1 to 14.97:1 across the eleven themes.
46
+ - **Peer presence is an avatar stack**, not one chip per peer. Eight people used
47
+ to mean eight buttons across the title bar; it is now three overlapping dots
48
+ and a `+N`, fixed width whatever the roster size. Hovering a dot opens the
49
+ usual card, clicking follows or unfollows, and the `+N` opens a list of
50
+ everyone else with a follow toggle per row.
51
+ - The title bar says "Mbeditor" rather than the full window title; the host and
52
+ port are in the browser's own address bar.
53
+ - The file-tree section header has no chevron. Collapsing it left an empty
54
+ sidebar, so there was nothing to collapse to.
55
+ - Tabs are rounded chips on the strip, the Problems panel wears VS Code chrome,
56
+ and problem counts are neutral rather than alarming.
57
+ - `.scss` files open as scss and `.less` files as less, instead of css and
58
+ plaintext.
59
+ - Audit recording moved off the subprocess critical path.
60
+
61
+ ### Fixed
62
+ - **The pending-migration warning stuck after `rails db:migrate`**, and
63
+ flickered before that. The header announcing it was only *set* when a
64
+ migration was pending, and those responses are conditional-GET cacheable: a
65
+ 304 updates the stored headers of the cached 200 but never removes one it
66
+ omits, so the browser replayed "pending" for as long as the cache entry
67
+ lived. It is now always sent, `1` or `0`, and an absent header counts as no
68
+ evidence either way.
69
+ - **The model graph was unusable on a large app.** The cost was in click and
70
+ hover, not pan and zoom: `focused` was a dependency of the scene memo, so
71
+ every click-to-centre rebuilt every SVG element inside the click handler;
72
+ node hover scanned the whole scene per mouseenter; and the hover card and
73
+ edge tooltip each read `getBoundingClientRect` per pointer move, right after
74
+ hover had dirtied the style, forcing a full-scene layout. Measured on a
75
+ 420-model, 1,249-association app: click-to-centre 20.4 ms → 7.9 ms median,
76
+ hover 45.7 ms → 33.2 ms.
77
+ - Collaboration data-loss paths, and the per-keystroke buffer work that went
78
+ with them.
79
+ - Undo tracking starts after the file load, not before, so the first undo no
80
+ longer empties a freshly opened file.
81
+ - Ruby hover skips comments and the route-hint decoration space, and
82
+ go-to-definition starts the Ripper fallback alongside ruby-lsp rather than
83
+ after it.
84
+ - Closing a markdown file closes its preview, and source and preview scroll in
85
+ sync.
86
+ - Markdown anchor links, upload UX, the conflict chip and the cursor readout.
87
+ - `RubyLspClient` performs its handshake outside the state lock, bounds the
88
+ document-lock wait, and keeps an LRU of documents.
89
+ - `JsSyntaxCheckService` remembers a broken babel bundle instead of retrying it,
90
+ and bounds its lock wait.
91
+ - The test-result `localStorage` cache is capped, and failed draft backups are
92
+ surfaced rather than silently dropped.
93
+ - `json` is pinned below 3 so the suite runs.
94
+
95
+ ### Removed
96
+ - Stale agent docs, the dead `build_js` rake task and a set of duplicated
97
+ helpers — about 1,100 lines.
98
+
99
+ ## [0.13.1] - 2026-08-26
100
+
101
+ ### Removed
102
+ - **The whole-suite test run is gone** — the status-bar "Tests" button, the
103
+ Test Run drawer, `POST /test_all`, and the `test_all_command` /
104
+ `test_all_timeout` settings. It was one blocking request holding a
105
+ development-server thread for the length of the suite, with no way to cancel
106
+ it and the runner's output buffered whole in the server process; on a real
107
+ host app that is twenty minutes of a frozen-looking button for a result the
108
+ terminal gives you faster and interruptibly. The per-file (and per-line) test
109
+ run stays — that one is scoped, finishes in seconds and is why the editor
110
+ runs tests at all. **A host app setting `test_all_command` or
111
+ `test_all_timeout` in its initializer must drop those lines.**
112
+ - **The ruby-lsp status-bar chip is gone**, along with its 10-second health
113
+ poll. ruby-lsp itself is untouched — definitions, hover, completion,
114
+ diagnostics and the rest all still run through it.
115
+
116
+ ### Added
117
+ - `rake mbeditor:scan_duplicates` — scans the workspace for files that contain a
118
+ duplicated copy of themselves, for finding files damaged before the seeding fix below.
119
+ Set `MBEDITOR_WORKSPACE_ROOT` to scan a tree other than the resolved workspace.
120
+
121
+ ### Fixed
122
+ - **Data loss: a saved file could contain two copies of itself.** When two editor
123
+ clients attached to a collaborative room the server had no state for — a fresh
124
+ room, or one evicted while both sat idle — each one seeded the shared document
125
+ from disk, and Yjs merged both inserts at offset 0 into two concatenated copies.
126
+ Saving then wrote the doubled buffer. The server now grants the right to seed a
127
+ room to exactly one client (`CollaborationDocStore.claim_seed`), never evicts a
128
+ room that still has subscribers, and a client whose cable reconnects re-publishes
129
+ its snapshot so a server that lost its state cannot hand out a second seed.
130
+ - **Status bar alignment.** Every item now sits in the same 8px cell instead of
131
+ carrying its own padding on top of the bar's (the branch, the offline chip
132
+ and the version each had a different one), the branch drops from 13px to the
133
+ bar's 11px, and `line-height: 1` on the bar *and* on its buttons puts an icon
134
+ and the number beside it in identical line boxes — Pico gives `<button>` its
135
+ own 1.5, so the problem counts sat in a 16.5px box next to an 11px icon.
136
+ - **The Problems panel's Fix button did nothing visible.** `rubocop -a` writes
137
+ its corrections to disk, but reports the offenses it just fixed alongside the
138
+ ones it left — each flagged both `corrected` and `correctable` — so the list
139
+ after a fix was byte-identical to the list before it and the button stayed
140
+ lit. Corrected offenses are now dropped from the response, open buffers are
141
+ re-read after the run, and clearing stale diagnostics now clears the React
142
+ marker map as well as Monaco's, which is what had left the panel, the tab
143
+ badges and the status-bar tallies disagreeing.
144
+ - **Quick Open matches loosely again.** MiniSearch was configured `fuzzy: false`,
145
+ so a single typo found nothing; fuzziness is now scaled per term. Folders sort
146
+ after files instead of ahead of every match, and the dialog and its text are
147
+ larger.
148
+ - **Search results read like VS Code's.** Hits group under a collapsible row per
149
+ file with a match count, the matched text is tinted with the accent colour and
150
+ is kept on screen at any panel width (eliding left and right around it), and
151
+ clicking a hit selects the match and leaves the cursor at its end.
152
+ - **Bulk import no longer opens on the previous import's folder** — the folder
153
+ you actually acted on is offered first — and it now reports what it wrote and
154
+ reveals the destination in the tree.
155
+ - `ReactRailsUJS` no longer reports as undefined; it ships inside the react-rails
156
+ gem, outside the workspace, so no static scan of the tree could ever find it.
157
+ - `<<~JSX` heredocs highlight as JavaScript (as do `<<~TSX`/`<<~TS`).
158
+ - The Open Editors list no longer runs its filenames underneath the split and
159
+ close buttons.
160
+
161
+ ### Changed
162
+ - **The activity bar is a third narrower** (60px to 40px), and the editor
163
+ toolbar's per-file Test button is gone; running the test under the cursor is
164
+ unaffected.
9
165
 
10
166
  ## [0.13.0] - 2026-08-20
11
167
 
@@ -1,4 +1,5 @@
1
1
  //= require mbeditor/application_iife_head
2
+ //= require mbeditor/audit_log
2
3
  //= require mbeditor/editor_store
3
4
  //= require mbeditor/file_icon
4
5
  //= require mbeditor/file_service
@@ -17,6 +18,8 @@
17
18
  //= require mbeditor/ruby_outline
18
19
  //= require mbeditor/js_outline
19
20
  //= require mbeditor/components/CollapsibleSection
21
+ //= require mbeditor/components/SettingsModal
22
+ //= require mbeditor/components/Gutter
20
23
  //= require mbeditor/components/ShortcutHelp
21
24
  //= require mbeditor/components/DiffViewer
22
25
  //= require mbeditor/components/CombinedDiffViewer
@@ -27,7 +30,6 @@
27
30
  //= require mbeditor/components/TestResultsPanel
28
31
  //= require mbeditor/components/LogPanel
29
32
  //= require mbeditor/components/ProblemsPanel
30
- //= require mbeditor/components/TestRunPanel
31
33
  //= require mbeditor/components/CodeReviewPanel
32
34
  //= require mbeditor/components/EditorPanel
33
35
  //= require mbeditor/components/FileTree
@@ -0,0 +1,165 @@
1
+ // Audit/telemetry ring the developer can download and hand to an AI to analyse.
2
+ //
3
+ // The privacy guarantee is structural, not a scrubber: rec() is the only way
4
+ // in, and it drops any argument whose typeof is not 'number'. A path, a file
5
+ // name, a URL or a line of source cannot be represented at all. Enumerated
6
+ // dimensions (file extension, search backend tier, LSP method, collab phase)
7
+ // travel as small integers whose meaning lives in LEGEND. LEGEND stays here and
8
+ // is merged into the file at download time: sending it to the server would be
9
+ // the one path in this module that carries an arbitrary string, which is
10
+ // exactly the property the rest of it exists to deny.
11
+ window.MbeditorAudit = (function () {
12
+ var CAPACITY = 512;
13
+ var FLUSH_AT = CAPACITY / 2;
14
+
15
+ var EV = Object.freeze({
16
+ BOOT: 1,
17
+ OPEN: 2,
18
+ SAVE: 3,
19
+ SEARCH: 4,
20
+ TREE_POLL: 5,
21
+ GIT_POLL: 6,
22
+ LINT: 7,
23
+ FORMAT: 8,
24
+ LSP: 9,
25
+ COLLAB: 10,
26
+ LONGTASK: 11,
27
+ ERR: 12
28
+ });
29
+
30
+ var LEGEND = {
31
+ ev: {},
32
+ // What a, b and c mean per event. A field named after one of the
33
+ // enumerated dimensions below carries an index into that table.
34
+ fields: {
35
+ BOOT: ['msToReady', '', ''],
36
+ OPEN: ['ext', 'bytes', 'ms'],
37
+ SAVE: ['ext', 'bytes', 'ms'],
38
+ SEARCH: ['searchBackend', 'ms', 'hits'],
39
+ TREE_POLL: ['ms', 'changed', ''],
40
+ GIT_POLL: ['ms', 'changedFiles', ''],
41
+ LINT: ['ext', 'ms', 'markers'],
42
+ FORMAT: ['ext', 'ms', 'bytes'],
43
+ LSP: ['lspMethod', 'ms', 'ok'],
44
+ COLLAB: ['collabPhase', 'peers', 'ms'],
45
+ LONGTASK: ['ms', '', ''],
46
+ ERR: ['ev', '', '']
47
+ },
48
+ // Index 0 is always "other", so an unrecognised name still records.
49
+ ext: ['other', 'rb', 'js', 'jsx', 'erb', 'haml', 'css', 'scss', 'html', 'md', 'json', 'yml', 'ts', 'tsx'],
50
+ searchBackend: ['other', 'rg', 'git', 'grep'],
51
+ lspMethod: ['other', 'definition', 'hover', 'completion', 'diagnostics', 'references', 'rename',
52
+ 'format', 'signatureHelp', 'documentSymbol', 'foldingRange', 'documentHighlight',
53
+ 'selectionRange', 'codeAction'],
54
+ collabPhase: ['other', 'join', 'seed', 'attach', 'defer', 'update', 'reconnect', 'leave', 'degrade']
55
+ };
56
+ Object.keys(EV).forEach(function (name) { LEGEND.ev[EV[name]] = name; });
57
+
58
+ // Preallocated: rec() reuses the slot, so recording allocates nothing.
59
+ var ring = new Array(CAPACITY);
60
+ for (var i = 0; i < CAPACITY; i++) ring[i] = [0, 0, 0, 0, 0];
61
+
62
+ var write = 0;
63
+ var count = 0;
64
+ var enabled = false;
65
+ var scheduled = false;
66
+ var observing = false;
67
+
68
+ // The whole privacy guarantee in one line.
69
+ function num(v) { return typeof v === 'number' && isFinite(v) ? v : 0; }
70
+
71
+ function rec(ev, a, b, c) {
72
+ if (!enabled) return;
73
+ var e = ring[write];
74
+ e[0] = performance.now() | 0;
75
+ e[1] = num(ev);
76
+ e[2] = num(a);
77
+ e[3] = num(b);
78
+ e[4] = num(c);
79
+ write = write + 1 === CAPACITY ? 0 : write + 1;
80
+ if (count < CAPACITY) count++;
81
+ if (count >= FLUSH_AT) schedule();
82
+ }
83
+
84
+ function code(dimension, name) {
85
+ var table = LEGEND[dimension];
86
+ var index = table ? table.indexOf(name) : -1;
87
+ return index < 0 ? 0 : index;
88
+ }
89
+
90
+ function drain() {
91
+ var out = new Array(count);
92
+ var start = (write - count + CAPACITY) % CAPACITY;
93
+ for (var i = 0; i < count; i++) {
94
+ var e = ring[(start + i) % CAPACITY];
95
+ out[i] = [e[0], e[1], e[2], e[3], e[4]];
96
+ }
97
+ count = 0;
98
+ write = 0;
99
+ return out;
100
+ }
101
+
102
+ // keepalive rather than navigator.sendBeacon: the CSRF guard needs the
103
+ // X-Mbeditor-Client header and sendBeacon cannot set one.
104
+ function flush() {
105
+ scheduled = false;
106
+ if (!enabled || count === 0) return Promise.resolve();
107
+ var body = JSON.stringify({ events: drain() });
108
+ return fetch(window.mbeditorBasePath() + '/audit_log', {
109
+ method: 'POST',
110
+ keepalive: true,
111
+ headers: { 'X-Mbeditor-Client': '1', 'Content-Type': 'application/json' },
112
+ body: body
113
+ })['catch'](function () {});
114
+ }
115
+
116
+ function schedule() {
117
+ if (scheduled) return;
118
+ scheduled = true;
119
+ if (window.requestIdleCallback) {
120
+ window.requestIdleCallback(function () { flush(); }, { timeout: 2000 });
121
+ } else {
122
+ setTimeout(function () { flush(); }, 0);
123
+ }
124
+ }
125
+
126
+ function observe() {
127
+ if (observing) return;
128
+ observing = true;
129
+ try {
130
+ new PerformanceObserver(function (list) {
131
+ var items = list.getEntries();
132
+ for (var i = 0; i < items.length; i++) rec(EV.LONGTASK, items[i].duration | 0);
133
+ }).observe({ type: 'longtask', buffered: true });
134
+ } catch (e) {
135
+ // Safari has no longtask entry type. Everything else keeps recording.
136
+ }
137
+ }
138
+
139
+ function clear() { count = 0; write = 0; }
140
+
141
+ function setEnabled(on) {
142
+ on = !!on;
143
+ if (on === enabled) return;
144
+ enabled = on;
145
+ // Disabled means nothing recorded, including what was buffered before the
146
+ // preference arrived from the server.
147
+ if (on) observe(); else clear();
148
+ }
149
+
150
+ window.addEventListener('pagehide', function () { flush(); });
151
+ document.addEventListener('visibilitychange', function () {
152
+ if (document.visibilityState === 'hidden') flush();
153
+ });
154
+
155
+ return {
156
+ EV: EV,
157
+ LEGEND: LEGEND,
158
+ rec: rec,
159
+ code: code,
160
+ flush: flush,
161
+ clear: clear,
162
+ setEnabled: setEnabled,
163
+ buffered: function () { return count; }
164
+ };
165
+ })();