trmnl_preview 0.15.0 → 0.17.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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +48 -0
  4. data/db/data/and_x_more.yml +86 -0
  5. data/db/data/locales/cs.yml +339 -0
  6. data/db/data/locales/da.yml +339 -0
  7. data/db/data/locales/de-AT.yml +341 -0
  8. data/db/data/locales/de.yml +341 -0
  9. data/db/data/locales/en-AU.yml +79 -0
  10. data/db/data/locales/en-GB.yml +79 -0
  11. data/db/data/locales/en.yml +341 -0
  12. data/db/data/locales/es-ES.yml +341 -0
  13. data/db/data/locales/fr.yml +339 -0
  14. data/db/data/locales/he.yml +339 -0
  15. data/db/data/locales/hu.yml +339 -0
  16. data/db/data/locales/id.yml +339 -0
  17. data/db/data/locales/is.yml +339 -0
  18. data/db/data/locales/it.yml +341 -0
  19. data/db/data/locales/ja.yml +342 -0
  20. data/db/data/locales/ko.yml +337 -0
  21. data/db/data/locales/lt.yml +340 -0
  22. data/db/data/locales/nl.yml +339 -0
  23. data/db/data/locales/no.yml +4 -0
  24. data/db/data/locales/pl.yml +342 -0
  25. data/db/data/locales/pt-BR.yml +340 -0
  26. data/db/data/locales/raw.yml +256 -0
  27. data/db/data/locales/ro.yml +339 -0
  28. data/db/data/locales/ru.yml +342 -0
  29. data/db/data/locales/sk.yml +339 -0
  30. data/db/data/locales/sv.yml +340 -0
  31. data/db/data/locales/uk.yml +339 -0
  32. data/db/data/locales/zh-CN.yml +338 -0
  33. data/db/data/locales/zh-HK.yml +338 -0
  34. data/lib/trmnlp/app.rb +5 -4
  35. data/lib/trmnlp/async_callback.rb +75 -0
  36. data/lib/trmnlp/browser_pool.rb +8 -8
  37. data/lib/trmnlp/cli.rb +7 -0
  38. data/lib/trmnlp/commands/serve.rb +3 -0
  39. data/lib/trmnlp/commands/test.rb +25 -0
  40. data/lib/trmnlp/config/plugin.rb +52 -12
  41. data/lib/trmnlp/config/project.rb +23 -10
  42. data/lib/trmnlp/config.rb +2 -2
  43. data/lib/trmnlp/context.rb +29 -6
  44. data/lib/trmnlp/errors.rb +1 -0
  45. data/lib/trmnlp/firefox_driver.rb +0 -3
  46. data/lib/trmnlp/framework_version.rb +9 -0
  47. data/lib/trmnlp/image_quantizer.rb +28 -15
  48. data/lib/trmnlp/lint/checks/no_custom_filters.rb +27 -0
  49. data/lib/trmnlp/lint/diagnostic.rb +5 -0
  50. data/lib/trmnlp/lint/source.rb +6 -0
  51. data/lib/trmnlp/lint.rb +3 -1
  52. data/lib/trmnlp/merged_plugin_locals.rb +57 -0
  53. data/lib/trmnlp/outbound_request.rb +99 -0
  54. data/lib/trmnlp/paths.rb +15 -8
  55. data/lib/trmnlp/poller.rb +100 -47
  56. data/lib/trmnlp/qr_code_intrinsic_size.rb +22 -0
  57. data/lib/trmnlp/rails_helpers.rb +41 -0
  58. data/lib/trmnlp/renderer.rb +40 -11
  59. data/lib/trmnlp/screen_generator.rb +14 -2
  60. data/lib/trmnlp/screenshot.rb +50 -11
  61. data/lib/trmnlp/select_field_choices.rb +25 -0
  62. data/lib/trmnlp/testing/browser.rb +87 -0
  63. data/lib/trmnlp/testing/certificate_authority.rb +84 -0
  64. data/lib/trmnlp/testing/device_models.rb +80 -0
  65. data/lib/trmnlp/testing/frozen_clock.rb +86 -0
  66. data/lib/trmnlp/testing/inline_stylesheets.rb +41 -0
  67. data/lib/trmnlp/testing/memory_sampler.rb +44 -0
  68. data/lib/trmnlp/testing/mock_proxy.rb +124 -0
  69. data/lib/trmnlp/testing/mock_table.rb +102 -0
  70. data/lib/trmnlp/testing/node_version.rb +27 -0
  71. data/lib/trmnlp/testing/plugin.rb +91 -0
  72. data/lib/trmnlp/testing/report.html.erb +79 -0
  73. data/lib/trmnlp/testing/report.rb +91 -0
  74. data/lib/trmnlp/testing/rspec.rb +89 -0
  75. data/lib/trmnlp/testing/run.rb +150 -0
  76. data/lib/trmnlp/testing/screen.rb +83 -0
  77. data/lib/trmnlp/testing/snapshot.rb +55 -0
  78. data/lib/trmnlp/transform_backend/subprocess.rb +8 -1
  79. data/lib/trmnlp/transform_pipeline.rb +51 -9
  80. data/lib/trmnlp/transform_state.rb +51 -0
  81. data/lib/trmnlp/user_data_assembler.rb +63 -25
  82. data/lib/trmnlp/version.rb +1 -1
  83. data/lib/trmnlp/watcher.rb +2 -1
  84. data/lib/trmnlp/webhook_payload.rb +74 -0
  85. data/lib/trmnlp/webhook_receiver.rb +78 -0
  86. data/lib/trmnlp.rb +2 -1
  87. data/templates/init/.github/workflows/trmnl.yml +33 -2
  88. data/templates/init/.gitignore +3 -0
  89. data/templates/init/tests/plugin_spec.rb +32 -0
  90. data/trmnl_preview.gemspec +6 -0
  91. data/web/public/index.js +18 -6
  92. data/web/views/index.erb +7 -0
  93. data/web/views/render_html.erb +37 -1
  94. metadata +115 -1
@@ -3,6 +3,16 @@ on:
3
3
  pull_request:
4
4
  push:
5
5
  branches: [main]
6
+ workflow_dispatch:
7
+ inputs:
8
+ update_snapshots:
9
+ description: Rewrite the snapshots in tests/snapshots and upload them
10
+ type: boolean
11
+ default: false
12
+
13
+ env:
14
+ # The trmnlp image the tests run in. Pin a release (trmnl/trmnlp:v0.17.0) to keep runs repeatable.
15
+ TRMNLP_IMAGE: trmnl/trmnlp:latest
6
16
 
7
17
  jobs:
8
18
  lint:
@@ -15,9 +25,30 @@ jobs:
15
25
  - run: gem install trmnl_preview
16
26
  - run: trmnlp lint
17
27
 
28
+ test:
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@v6
32
+ - name: trmnlp test
33
+ run: >
34
+ docker run --rm -e CI=true -e GITHUB_STEP_SUMMARY=/summary.md
35
+ -v "$PWD":/plugin -v "$GITHUB_STEP_SUMMARY":/summary.md "$TRMNLP_IMAGE"
36
+ test --report report ${{ inputs.update_snapshots && '--update' || '' }}
37
+ - if: always()
38
+ uses: actions/upload-artifact@v4
39
+ with:
40
+ name: trmnlp-report
41
+ path: report
42
+ - if: ${{ inputs.update_snapshots }}
43
+ uses: actions/upload-artifact@v4
44
+ with:
45
+ name: snapshots
46
+ path: tests/snapshots
47
+
18
48
  push:
19
- needs: lint
20
- if: github.ref == 'refs/heads/main'
49
+ # Take test out of needs to push to TRMNL even when the tests fail.
50
+ needs: [lint, test]
51
+ if: github.ref == 'refs/heads/main' && github.event_name == 'push'
21
52
  runs-on: ubuntu-latest
22
53
  steps:
23
54
  - uses: actions/checkout@v6
@@ -1,2 +1,5 @@
1
1
  # trmnlp build output
2
2
  _build/
3
+
4
+ # trmnlp test --report output
5
+ report/
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This plugin's tests, run with `trmnlp test` (add `--report report` for a page of every screen it drew).
4
+ # Docs: https://github.com/usetrmnl/trmnlp#testing-plugins
5
+ RSpec.describe 'My Plugin' do
6
+ # Canned answers for the requests the plugin makes, its polling urls and its transform's own, e.g.
7
+ # { 'https://api.example.com/*' => { json: { temperature: 21 } } }
8
+ let(:mocks) { {} }
9
+
10
+ # The values of the custom fields in src/settings.yml to test with.
11
+ let(:custom_fields) { {} }
12
+
13
+ %w[full half_horizontal half_vertical quadrant].each do |view|
14
+ it "draws the #{view} view without overflow or page errors" do
15
+ screen = trmnl.render(view:, mocks:, custom_fields:)
16
+
17
+ expect(screen).to have_no_overflow
18
+ expect(screen).to have_no_problems
19
+ end
20
+ end
21
+
22
+ it "stays within TRMNL's serverless limits" do
23
+ expect(trmnl.transform(mocks:, custom_fields:)).to stay_within_serverless_limits
24
+ end
25
+
26
+ # A picture of the full view, compared pixel for pixel on every run. Record it once with
27
+ # `trmnlp test --update` (or the workflow's update_snapshots run) and commit tests/snapshots.
28
+ #
29
+ # it 'looks as it did' do
30
+ # expect(trmnl.render(mocks:, custom_fields:)).to match_snapshot
31
+ # end
32
+ end
@@ -48,6 +48,7 @@ Gem::Specification.new do |spec|
48
48
  # HTML rendering
49
49
  spec.add_dependency 'activesupport', '~> 8.0'
50
50
  spec.add_dependency 'crass', '~> 1.0'
51
+ spec.add_dependency 'csv', '~> 3.3'
51
52
  spec.add_dependency 'nokogiri', '~> 1.18'
52
53
  spec.add_dependency 'trmnl-liquid', '~> 0.8.2'
53
54
 
@@ -56,6 +57,11 @@ Gem::Specification.new do |spec|
56
57
  spec.add_dependency 'mini_magick', '~> 5.3'
57
58
  spec.add_dependency 'selenium-webdriver', '~> 4.44'
58
59
 
60
+ # Plugin tests (`trmnlp test`)
61
+ spec.add_dependency 'capybara', '~> 3.40'
62
+ spec.add_dependency 'rspec-core', '~> 3.13'
63
+ spec.add_dependency 'rspec-expectations', '~> 3.13'
64
+
59
65
  # OAuth2 (local plugin preview auth flow)
60
66
  spec.add_dependency 'oauth2', '~> 2.0'
61
67
 
data/web/public/index.js CHANGED
@@ -23,7 +23,8 @@ trmnlp.connectLiveRender = function () {
23
23
  // trmnl.device values the picked model and palette decide.
24
24
  trmnlp.deviceParams = function (state) {
25
25
  const bitDepth = Math.ceil(Math.log2(state.palette?.grays || 2));
26
- return new URLSearchParams({ width: state.width, height: state.height, model: state.model.name, bit_depth: bitDepth });
26
+ const orientation = state.isPortrait ? 'portrait' : 'landscape';
27
+ return new URLSearchParams({ width: state.width, height: state.height, model: state.model.name, bit_depth: bitDepth, orientation });
27
28
  };
28
29
 
29
30
  trmnlp.fetchPreview = function (pickerState) {
@@ -31,6 +32,7 @@ trmnlp.fetchPreview = function (pickerState) {
31
32
  const screenClasses = (state?.screenClasses || []).join(" ");
32
33
  const encodedScreenClasses = encodeURIComponent(screenClasses);
33
34
  let src = `/render/${trmnlp.view}.${trmnlp.formatSelect.value}?screen_classes=${encodedScreenClasses}`;
35
+ if (trmnlp.themeSelect.value) src += `&theme=${encodeURIComponent(trmnlp.themeSelect.value)}`;
34
36
 
35
37
  // Pass the device for both HTML and PNG renders so trmnl.device.{width,height,model,bit_depth}
36
38
  // in the Liquid context tracks the picker model selection.
@@ -38,9 +40,8 @@ trmnlp.fetchPreview = function (pickerState) {
38
40
  src += `&${trmnlp.deviceParams(state)}`;
39
41
  }
40
42
 
41
- // PNG-only: dark mode + color depth from palette
43
+ // PNG-only: color depth from palette
42
44
  if (trmnlp.formatSelect.value === 'png' && state) {
43
- const isDarkMode = state.isDarkMode ? 1 : 0;
44
45
  const grays = state.palette.grays || 2;
45
46
  const colorDepth = Math.ceil(Math.log2(grays));
46
47
  src += `&color_depth=${colorDepth}`;
@@ -214,9 +215,13 @@ trmnlp.addSelectFieldRow = function (container, key, selectedValue, options) {
214
215
  const row = document.createElement('div');
215
216
  row.className = 'custom-field-row';
216
217
 
217
- const optionsHtml = options.map(opt => {
218
- const selected = String(opt) === String(selectedValue) ? 'selected' : '';
219
- return `<option value="${trmnlp.escapeHtml(String(opt))}" ${selected}>${trmnlp.escapeHtml(String(opt))}</option>`;
218
+ // Saved as TRMNL saves them: "New York" as new_york, { Label: value } as its value.
219
+ const choices = options.map(opt => (opt && typeof opt === 'object')
220
+ ? [String(Object.keys(opt)[0]), String(Object.values(opt)[0])]
221
+ : [String(opt), String(opt).toLowerCase().replaceAll(' ', '_')]);
222
+ const optionsHtml = choices.map(([label, value]) => {
223
+ const selected = [label, value].includes(String(selectedValue)) ? 'selected' : '';
224
+ return `<option value="${trmnlp.escapeHtml(value)}" ${selected}>${trmnlp.escapeHtml(label)}</option>`;
220
225
  }).join('');
221
226
 
222
227
  row.innerHTML = `
@@ -261,6 +266,7 @@ document.addEventListener("DOMContentLoaded", async function () {
261
266
  trmnlp.view = document.querySelector("meta[name='trmnl-view']").content;
262
267
  trmnlp.iframe = document.querySelector("iframe");
263
268
  trmnlp.formatSelect = document.querySelector(".select-format");
269
+ trmnlp.themeSelect = document.querySelector("[data-theme-select]");
264
270
  trmnlp.userData = document.getElementById("user-data");
265
271
  trmnlp.spinner = document.querySelector(".spinner");
266
272
  trmnlp.isLiveReloadEnabled =
@@ -278,6 +284,12 @@ document.addEventListener("DOMContentLoaded", async function () {
278
284
  trmnlp.fetchPreview();
279
285
  });
280
286
 
287
+ trmnlp.themeSelect.value = localStorage.getItem("trmnlp-theme") || "";
288
+ trmnlp.themeSelect.addEventListener("change", () => {
289
+ localStorage.setItem("trmnlp-theme", trmnlp.themeSelect.value);
290
+ trmnlp.fetchPreview();
291
+ });
292
+
281
293
  trmnlp.iframe.addEventListener("load", () => {
282
294
  trmnlp.spinner.style.display = "none";
283
295
  });
data/web/views/index.erb CHANGED
@@ -46,6 +46,13 @@
46
46
  <span data-dark-mode-text>Light Mode</span>
47
47
  </button>
48
48
 
49
+ <select data-theme-select>
50
+ <option value="">Default theme</option>
51
+ <% TRMNLP::FrameworkVersion::THEMES.each do |id, name| %>
52
+ <option value="<%= id %>"><%= name %></option>
53
+ <% end %>
54
+ </select>
55
+
49
56
  <!-- Optional: Reset button -->
50
57
  <button class="btn" type="button" data-reset-button>Reset</button>
51
58
  </form>
@@ -1,8 +1,44 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
+ <script>
5
+ const [andXMorePrefix, andXMoreSuffix] = <%= @and_x_more.to_json %>;
6
+ window.I18n = { andXMore: (count) => `${andXMorePrefix} ${count} ${andXMoreSuffix}` };
7
+
8
+ // Copied from TRMNL's plugin_rendering.js: the PNG capture waits for TRMNL_HIGHCHARTS_DONE.
9
+ document.addEventListener("DOMContentLoaded", () => {
10
+ if (!window.Highcharts) {
11
+ window.TRMNL_HIGHCHARTS_DONE = true;
12
+ return;
13
+ }
14
+
15
+ let counter = 0;
16
+ const settle = () => {
17
+ const allReady = Highcharts.charts.every(c => c && c.renderer && c.renderer.boxWrapper);
18
+ if (!allReady && counter <= 50) return false; // 5 second timeout
19
+
20
+ // A chart drawn before the title bar clamped its slot falls back to a 400px height.
21
+ Highcharts.charts.forEach(c => {
22
+ if (!c || !c.renderTo) return;
23
+ const w = c.renderTo.offsetWidth;
24
+ const h = c.renderTo.offsetHeight;
25
+ if (h && Math.round(c.chartHeight) !== Math.round(h)) c.setSize(w, h, false);
26
+ });
27
+ window.TRMNL_HIGHCHARTS_DONE = true;
28
+ return true;
29
+ };
30
+
31
+ if (settle()) return;
32
+
33
+ const interval = setInterval(() => {
34
+ counter++;
35
+ if (settle()) clearInterval(interval);
36
+ }, 100);
37
+ });
38
+ </script>
4
39
  <link rel="stylesheet" href="<%= @framework.css_url %>" />
5
- <script src="<%= @framework.js_url %>"></script>
40
+ <% if @theme_css_url %><link rel="stylesheet" href="<%= @theme_css_url %>" /><% end %>
41
+ <script type="module" src="<%= @framework.js_url %>"></script>
6
42
  <meta name="trmnl-framework-version" content="<%= @framework.number %>" />
7
43
  <meta name="trmnl-framework-pinned" content="<%= @framework.pinned? %>" />
8
44
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trmnl_preview
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rockwell Schrock
@@ -79,6 +79,20 @@ dependencies:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '1.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: csv
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.3'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.3'
82
96
  - !ruby/object:Gem::Dependency
83
97
  name: nokogiri
84
98
  requirement: !ruby/object:Gem::Requirement
@@ -135,6 +149,48 @@ dependencies:
135
149
  - - "~>"
136
150
  - !ruby/object:Gem::Version
137
151
  version: '4.44'
152
+ - !ruby/object:Gem::Dependency
153
+ name: capybara
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '3.40'
159
+ type: :runtime
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '3.40'
166
+ - !ruby/object:Gem::Dependency
167
+ name: rspec-core
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '3.13'
173
+ type: :runtime
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: '3.13'
180
+ - !ruby/object:Gem::Dependency
181
+ name: rspec-expectations
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: '3.13'
187
+ type: :runtime
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - "~>"
192
+ - !ruby/object:Gem::Version
193
+ version: '3.13'
138
194
  - !ruby/object:Gem::Dependency
139
195
  name: oauth2
140
196
  requirement: !ruby/object:Gem::Requirement
@@ -302,11 +358,42 @@ files:
302
358
  - README.md
303
359
  - bin/rake
304
360
  - bin/trmnlp
361
+ - db/data/and_x_more.yml
305
362
  - db/data/form_fields.yml
306
363
  - db/data/framework_versions.yml
364
+ - db/data/locales/cs.yml
365
+ - db/data/locales/da.yml
366
+ - db/data/locales/de-AT.yml
367
+ - db/data/locales/de.yml
368
+ - db/data/locales/en-AU.yml
369
+ - db/data/locales/en-GB.yml
370
+ - db/data/locales/en.yml
371
+ - db/data/locales/es-ES.yml
372
+ - db/data/locales/fr.yml
373
+ - db/data/locales/he.yml
374
+ - db/data/locales/hu.yml
375
+ - db/data/locales/id.yml
376
+ - db/data/locales/is.yml
377
+ - db/data/locales/it.yml
378
+ - db/data/locales/ja.yml
379
+ - db/data/locales/ko.yml
380
+ - db/data/locales/lt.yml
381
+ - db/data/locales/nl.yml
382
+ - db/data/locales/no.yml
383
+ - db/data/locales/pl.yml
384
+ - db/data/locales/pt-BR.yml
385
+ - db/data/locales/raw.yml
386
+ - db/data/locales/ro.yml
387
+ - db/data/locales/ru.yml
388
+ - db/data/locales/sk.yml
389
+ - db/data/locales/sv.yml
390
+ - db/data/locales/uk.yml
391
+ - db/data/locales/zh-CN.yml
392
+ - db/data/locales/zh-HK.yml
307
393
  - lib/trmnlp.rb
308
394
  - lib/trmnlp/api_client.rb
309
395
  - lib/trmnlp/app.rb
396
+ - lib/trmnlp/async_callback.rb
310
397
  - lib/trmnlp/browser_pool.rb
311
398
  - lib/trmnlp/cli.rb
312
399
  - lib/trmnlp/commands.rb
@@ -320,6 +407,7 @@ files:
320
407
  - lib/trmnlp/commands/pull.rb
321
408
  - lib/trmnlp/commands/push.rb
322
409
  - lib/trmnlp/commands/serve.rb
410
+ - lib/trmnlp/commands/test.rb
323
411
  - lib/trmnlp/config.rb
324
412
  - lib/trmnlp/config/app.rb
325
413
  - lib/trmnlp/config/plugin.rb
@@ -342,6 +430,7 @@ files:
342
430
  - lib/trmnlp/lint/checks/layouts_have_content.rb
343
431
  - lib/trmnlp/lint/checks/limited_inline_styles.rb
344
432
  - lib/trmnlp/lint/checks/no_async_functions.rb
433
+ - lib/trmnlp/lint/checks/no_custom_filters.rb
345
434
  - lib/trmnlp/lint/checks/no_opacity.rb
346
435
  - lib/trmnlp/lint/checks/no_size_classes.rb
347
436
  - lib/trmnlp/lint/checks/recipe_overview_length.rb
@@ -350,6 +439,7 @@ files:
350
439
  - lib/trmnlp/lint/checks/waits_for_dom_load.rb
351
440
  - lib/trmnlp/lint/diagnostic.rb
352
441
  - lib/trmnlp/lint/source.rb
442
+ - lib/trmnlp/merged_plugin_locals.rb
353
443
  - lib/trmnlp/oauth.rb
354
444
  - lib/trmnlp/oauth/client.rb
355
445
  - lib/trmnlp/oauth/pkce.rb
@@ -357,22 +447,45 @@ files:
357
447
  - lib/trmnlp/oauth/session.rb
358
448
  - lib/trmnlp/oauth/token_bundle.rb
359
449
  - lib/trmnlp/oauth/token_store.rb
450
+ - lib/trmnlp/outbound_request.rb
360
451
  - lib/trmnlp/paths.rb
361
452
  - lib/trmnlp/poller.rb
453
+ - lib/trmnlp/qr_code_intrinsic_size.rb
454
+ - lib/trmnlp/rails_helpers.rb
362
455
  - lib/trmnlp/renderer.rb
363
456
  - lib/trmnlp/reporter.rb
364
457
  - lib/trmnlp/screen.rb
365
458
  - lib/trmnlp/screen_generator.rb
366
459
  - lib/trmnlp/screenshot.rb
460
+ - lib/trmnlp/select_field_choices.rb
461
+ - lib/trmnlp/testing/browser.rb
462
+ - lib/trmnlp/testing/certificate_authority.rb
463
+ - lib/trmnlp/testing/device_models.rb
464
+ - lib/trmnlp/testing/frozen_clock.rb
465
+ - lib/trmnlp/testing/inline_stylesheets.rb
466
+ - lib/trmnlp/testing/memory_sampler.rb
467
+ - lib/trmnlp/testing/mock_proxy.rb
468
+ - lib/trmnlp/testing/mock_table.rb
469
+ - lib/trmnlp/testing/node_version.rb
470
+ - lib/trmnlp/testing/plugin.rb
471
+ - lib/trmnlp/testing/report.html.erb
472
+ - lib/trmnlp/testing/report.rb
473
+ - lib/trmnlp/testing/rspec.rb
474
+ - lib/trmnlp/testing/run.rb
475
+ - lib/trmnlp/testing/screen.rb
476
+ - lib/trmnlp/testing/snapshot.rb
367
477
  - lib/trmnlp/transform_backend/http.rb
368
478
  - lib/trmnlp/transform_backend/subprocess.rb
369
479
  - lib/trmnlp/transform_backend/which.rb
370
480
  - lib/trmnlp/transform_backend/wrapper.rb
371
481
  - lib/trmnlp/transform_client.rb
372
482
  - lib/trmnlp/transform_pipeline.rb
483
+ - lib/trmnlp/transform_state.rb
373
484
  - lib/trmnlp/user_data_assembler.rb
374
485
  - lib/trmnlp/version.rb
375
486
  - lib/trmnlp/watcher.rb
487
+ - lib/trmnlp/webhook_payload.rb
488
+ - lib/trmnlp/webhook_receiver.rb
376
489
  - templates/init/.github/workflows/trmnl.yml
377
490
  - templates/init/.gitignore
378
491
  - templates/init/.trmnlp.yml
@@ -384,6 +497,7 @@ files:
384
497
  - templates/init/src/settings.yml
385
498
  - templates/init/src/shared.liquid
386
499
  - templates/init/src/transform.py.example
500
+ - templates/init/tests/plugin_spec.rb
387
501
  - trmnl_preview.gemspec
388
502
  - web/public/highlight/highlight.min.js
389
503
  - web/public/highlight/styles/atom-one-dark.min.css