tokra 0.0.1.pre.1 → 0.0.1.pre.2

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 (160) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSES/MIT-0.txt +16 -0
  3. data/REUSE.toml +6 -1
  4. data/Rakefile +2 -0
  5. data/Steepfile +2 -0
  6. data/clippy_exceptions.rb +75 -37
  7. data/doc/contributors/adr/004.md +63 -0
  8. data/doc/contributors/chats/002.md +177 -0
  9. data/doc/contributors/chats/003.md +2180 -0
  10. data/doc/contributors/chats/004.md +1992 -0
  11. data/doc/contributors/chats/005.md +1529 -0
  12. data/doc/contributors/plan/002.md +173 -0
  13. data/doc/contributors/plan/003.md +111 -0
  14. data/examples/verify_hello_world/index.html +15 -2
  15. data/examples/verify_ping_pong/app.rb +3 -1
  16. data/examples/verify_ping_pong/public/styles.css +36 -9
  17. data/examples/verify_ping_pong/views/layout.erb +1 -1
  18. data/examples/verify_rails_sqlite/.dockerignore +51 -0
  19. data/examples/verify_rails_sqlite/.gitattributes +9 -0
  20. data/examples/verify_rails_sqlite/.github/dependabot.yml +12 -0
  21. data/examples/verify_rails_sqlite/.github/workflows/ci.yml +124 -0
  22. data/examples/verify_rails_sqlite/.gitignore +35 -0
  23. data/examples/verify_rails_sqlite/.kamal/hooks/docker-setup.sample +3 -0
  24. data/examples/verify_rails_sqlite/.kamal/hooks/post-app-boot.sample +3 -0
  25. data/examples/verify_rails_sqlite/.kamal/hooks/post-deploy.sample +14 -0
  26. data/examples/verify_rails_sqlite/.kamal/hooks/post-proxy-reboot.sample +3 -0
  27. data/examples/verify_rails_sqlite/.kamal/hooks/pre-app-boot.sample +3 -0
  28. data/examples/verify_rails_sqlite/.kamal/hooks/pre-build.sample +51 -0
  29. data/examples/verify_rails_sqlite/.kamal/hooks/pre-connect.sample +47 -0
  30. data/examples/verify_rails_sqlite/.kamal/hooks/pre-deploy.sample +122 -0
  31. data/examples/verify_rails_sqlite/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  32. data/examples/verify_rails_sqlite/.kamal/secrets +20 -0
  33. data/examples/verify_rails_sqlite/.rubocop.yml +2 -0
  34. data/examples/verify_rails_sqlite/.ruby-version +1 -0
  35. data/examples/verify_rails_sqlite/Dockerfile +77 -0
  36. data/examples/verify_rails_sqlite/Gemfile +66 -0
  37. data/examples/verify_rails_sqlite/Gemfile.lock +563 -0
  38. data/examples/verify_rails_sqlite/README.md +41 -0
  39. data/examples/verify_rails_sqlite/Rakefile +6 -0
  40. data/examples/verify_rails_sqlite/app/assets/images/.keep +0 -0
  41. data/examples/verify_rails_sqlite/app/assets/stylesheets/application.css +469 -0
  42. data/examples/verify_rails_sqlite/app/controllers/application_controller.rb +12 -0
  43. data/examples/verify_rails_sqlite/app/controllers/concerns/.keep +0 -0
  44. data/examples/verify_rails_sqlite/app/controllers/todos_controller.rb +70 -0
  45. data/examples/verify_rails_sqlite/app/helpers/application_helper.rb +2 -0
  46. data/examples/verify_rails_sqlite/app/helpers/todos_helper.rb +2 -0
  47. data/examples/verify_rails_sqlite/app/javascript/application.js +3 -0
  48. data/examples/verify_rails_sqlite/app/javascript/controllers/application.js +9 -0
  49. data/examples/verify_rails_sqlite/app/javascript/controllers/hello_controller.js +7 -0
  50. data/examples/verify_rails_sqlite/app/javascript/controllers/index.js +4 -0
  51. data/examples/verify_rails_sqlite/app/jobs/application_job.rb +7 -0
  52. data/examples/verify_rails_sqlite/app/mailers/application_mailer.rb +4 -0
  53. data/examples/verify_rails_sqlite/app/models/application_record.rb +3 -0
  54. data/examples/verify_rails_sqlite/app/models/concerns/.keep +0 -0
  55. data/examples/verify_rails_sqlite/app/models/todo.rb +10 -0
  56. data/examples/verify_rails_sqlite/app/views/layouts/application.html.erb +24 -0
  57. data/examples/verify_rails_sqlite/app/views/layouts/mailer.html.erb +13 -0
  58. data/examples/verify_rails_sqlite/app/views/layouts/mailer.text.erb +1 -0
  59. data/examples/verify_rails_sqlite/app/views/pwa/manifest.json.erb +22 -0
  60. data/examples/verify_rails_sqlite/app/views/pwa/service-worker.js +26 -0
  61. data/examples/verify_rails_sqlite/app/views/todos/_form.html.erb +27 -0
  62. data/examples/verify_rails_sqlite/app/views/todos/_todo.html.erb +18 -0
  63. data/examples/verify_rails_sqlite/app/views/todos/_todo.json.jbuilder +2 -0
  64. data/examples/verify_rails_sqlite/app/views/todos/edit.html.erb +7 -0
  65. data/examples/verify_rails_sqlite/app/views/todos/index.html.erb +22 -0
  66. data/examples/verify_rails_sqlite/app/views/todos/index.json.jbuilder +1 -0
  67. data/examples/verify_rails_sqlite/app/views/todos/new.html.erb +7 -0
  68. data/examples/verify_rails_sqlite/app/views/todos/show.html.erb +23 -0
  69. data/examples/verify_rails_sqlite/app/views/todos/show.json.jbuilder +1 -0
  70. data/examples/verify_rails_sqlite/bin/brakeman +7 -0
  71. data/examples/verify_rails_sqlite/bin/bundler-audit +6 -0
  72. data/examples/verify_rails_sqlite/bin/ci +6 -0
  73. data/examples/verify_rails_sqlite/bin/dev +2 -0
  74. data/examples/verify_rails_sqlite/bin/docker-entrypoint +8 -0
  75. data/examples/verify_rails_sqlite/bin/importmap +4 -0
  76. data/examples/verify_rails_sqlite/bin/jobs +6 -0
  77. data/examples/verify_rails_sqlite/bin/kamal +16 -0
  78. data/examples/verify_rails_sqlite/bin/rails +4 -0
  79. data/examples/verify_rails_sqlite/bin/rake +4 -0
  80. data/examples/verify_rails_sqlite/bin/rubocop +8 -0
  81. data/examples/verify_rails_sqlite/bin/setup +35 -0
  82. data/examples/verify_rails_sqlite/bin/thrust +5 -0
  83. data/examples/verify_rails_sqlite/config/application.rb +27 -0
  84. data/examples/verify_rails_sqlite/config/boot.rb +4 -0
  85. data/examples/verify_rails_sqlite/config/bundler-audit.yml +5 -0
  86. data/examples/verify_rails_sqlite/config/cable.yml +17 -0
  87. data/examples/verify_rails_sqlite/config/cache.yml +16 -0
  88. data/examples/verify_rails_sqlite/config/ci.rb +24 -0
  89. data/examples/verify_rails_sqlite/config/credentials.yml.enc +1 -0
  90. data/examples/verify_rails_sqlite/config/database.yml +40 -0
  91. data/examples/verify_rails_sqlite/config/deploy.yml +119 -0
  92. data/examples/verify_rails_sqlite/config/environment.rb +5 -0
  93. data/examples/verify_rails_sqlite/config/environments/development.rb +84 -0
  94. data/examples/verify_rails_sqlite/config/environments/production.rb +99 -0
  95. data/examples/verify_rails_sqlite/config/environments/test.rb +53 -0
  96. data/examples/verify_rails_sqlite/config/importmap.rb +7 -0
  97. data/examples/verify_rails_sqlite/config/initializers/assets.rb +7 -0
  98. data/examples/verify_rails_sqlite/config/initializers/content_security_policy.rb +29 -0
  99. data/examples/verify_rails_sqlite/config/initializers/filter_parameter_logging.rb +8 -0
  100. data/examples/verify_rails_sqlite/config/initializers/inflections.rb +16 -0
  101. data/examples/verify_rails_sqlite/config/locales/en.yml +31 -0
  102. data/examples/verify_rails_sqlite/config/puma.rb +42 -0
  103. data/examples/verify_rails_sqlite/config/queue.yml +18 -0
  104. data/examples/verify_rails_sqlite/config/recurring.yml +15 -0
  105. data/examples/verify_rails_sqlite/config/routes.rb +15 -0
  106. data/examples/verify_rails_sqlite/config/storage.yml +27 -0
  107. data/examples/verify_rails_sqlite/config.ru +6 -0
  108. data/examples/verify_rails_sqlite/db/cable_schema.rb +11 -0
  109. data/examples/verify_rails_sqlite/db/cache_schema.rb +12 -0
  110. data/examples/verify_rails_sqlite/db/migrate/20260130080933_create_todos.rb +10 -0
  111. data/examples/verify_rails_sqlite/db/queue_schema.rb +129 -0
  112. data/examples/verify_rails_sqlite/db/schema.rb +20 -0
  113. data/examples/verify_rails_sqlite/db/seeds.rb +9 -0
  114. data/examples/verify_rails_sqlite/lib/tasks/.keep +0 -0
  115. data/examples/verify_rails_sqlite/log/.keep +0 -0
  116. data/examples/verify_rails_sqlite/public/400.html +135 -0
  117. data/examples/verify_rails_sqlite/public/404.html +135 -0
  118. data/examples/verify_rails_sqlite/public/406-unsupported-browser.html +135 -0
  119. data/examples/verify_rails_sqlite/public/422.html +135 -0
  120. data/examples/verify_rails_sqlite/public/500.html +135 -0
  121. data/examples/verify_rails_sqlite/public/icon.png +0 -0
  122. data/examples/verify_rails_sqlite/public/icon.svg +3 -0
  123. data/examples/verify_rails_sqlite/public/robots.txt +1 -0
  124. data/examples/verify_rails_sqlite/public/styles.css +469 -0
  125. data/examples/verify_rails_sqlite/script/.keep +0 -0
  126. data/examples/verify_rails_sqlite/storage/.keep +0 -0
  127. data/examples/verify_rails_sqlite/test/controllers/.keep +0 -0
  128. data/examples/verify_rails_sqlite/test/controllers/todos_controller_test.rb +48 -0
  129. data/examples/verify_rails_sqlite/test/fixtures/files/.keep +0 -0
  130. data/examples/verify_rails_sqlite/test/fixtures/todos.yml +9 -0
  131. data/examples/verify_rails_sqlite/test/helpers/.keep +0 -0
  132. data/examples/verify_rails_sqlite/test/integration/.keep +0 -0
  133. data/examples/verify_rails_sqlite/test/mailers/.keep +0 -0
  134. data/examples/verify_rails_sqlite/test/models/.keep +0 -0
  135. data/examples/verify_rails_sqlite/test/models/todo_test.rb +7 -0
  136. data/examples/verify_rails_sqlite/test/test_helper.rb +15 -0
  137. data/examples/verify_rails_sqlite/tmp/.keep +0 -0
  138. data/examples/verify_rails_sqlite/tmp/pids/.keep +0 -0
  139. data/examples/verify_rails_sqlite/tmp/storage/.keep +0 -0
  140. data/examples/verify_rails_sqlite/tokra.rb +42 -0
  141. data/examples/verify_rails_sqlite/vendor/.keep +0 -0
  142. data/examples/verify_rails_sqlite/vendor/javascript/.keep +0 -0
  143. data/ext/tokra/src/event_loop.rs +206 -0
  144. data/ext/tokra/src/events.rs +430 -0
  145. data/ext/tokra/src/lib.rs +52 -664
  146. data/ext/tokra/src/proxy.rs +142 -0
  147. data/ext/tokra/src/responders.rs +86 -0
  148. data/ext/tokra/src/user_event.rs +313 -0
  149. data/ext/tokra/src/webview.rs +194 -0
  150. data/ext/tokra/src/window.rs +92 -0
  151. data/lib/tokra/rack/handler.rb +37 -14
  152. data/lib/tokra/version.rb +1 -1
  153. data/rbs_exceptions.rb +12 -0
  154. data/sig/tokra.rbs +95 -1
  155. data/tasks/lint.rake +2 -2
  156. data/tasks/lint.rb +49 -0
  157. data/tasks/rust.rake +6 -23
  158. data/tasks/steep.rake +25 -3
  159. data/tasks/test.rake +1 -1
  160. metadata +143 -1
@@ -0,0 +1,173 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+
4
+ SPDX-License-Identifier: CC-BY-SA-4.0
5
+ -->
6
+
7
+ # Test Coverage Implementation Plan
8
+
9
+ ## Goal
10
+
11
+ Ensure every line of `ext/` and `lib/` is covered by high-quality tests that would stand up to mutation testing. Tests should verify behavior, not just call code.
12
+
13
+ ## Current State
14
+
15
+ | File | Lines | Current Tests | Coverage |
16
+ |------|-------|---------------|----------|
17
+ | `lib/tokra.rb` | 20 | 1 (version) | ~10% |
18
+ | `lib/tokra/version.rb` | 13 | 1 | 100% |
19
+ | `lib/tokra/native.rb` | 80 | 0 (docs only) | N/A |
20
+ | `lib/tokra/rack/handler.rb` | 184 | 0 | 0% |
21
+ | `ext/tokra/src/lib.rs` | 721 | 3 (invariants) | ~5% |
22
+
23
+ ## Test Strategy
24
+
25
+ Following Alexander Tarlinder's principles from _Developer Testing_:
26
+ - **Mutation-resistant**: Tests verify observable behavior, not implementation details
27
+ - **Classicist for results**: State verification for handler responses
28
+ - **Mockist for collaboration**: Spy on Proxy#respond calls to verify IPC flow
29
+ - **Library tests as documentation**: Each test describes expected usage
30
+
31
+ ---
32
+
33
+ ## Proposed Changes
34
+
35
+ ### Unit Tests: Rack Handler
36
+
37
+ #### [NEW] [test_rack_handler.rb](file:///Users/kerrick/Developer/tokra/test/test_rack_handler.rb)
38
+
39
+ Tests for `Rack::Handler::Tokra`:
40
+
41
+ 1. **`.run` method behavior**
42
+ - Creates EventLoop, Proxy, Window, WebView
43
+ - Accepts options (title, width, height)
44
+ - Accepts invoke_handler for IPC
45
+ - Merges DEFAULT_OPTIONS correctly
46
+
47
+ 2. **`#call` method - core request handling**
48
+ - Translates HttpRequestEvent URI into Rack env
49
+ - Calls app.call with correct env hash
50
+ - Returns response via proxy.respond
51
+ - Handles empty paths ("/" default)
52
+
53
+ 3. **Redirect following (mutation-critical!)**
54
+ - Follows 301/302/303 redirects internally
55
+ - Respects MAX_REDIRECTS (10)
56
+ - Returns 508 on redirect loop
57
+ - Preserves Set-Cookie across redirects (PRG pattern)
58
+ - Converts POST to GET after redirect
59
+
60
+ 4. **`#build_env` private method**
61
+ - Sets all required Rack env keys
62
+ - Handles query strings
63
+ - Sets proper rack.* keys
64
+ - Injects HTTP_COOKIE when provided
65
+
66
+ 5. **Error handling**
67
+ - Returns proper error response for redirect overflow
68
+
69
+ ---
70
+
71
+ ### Unit Tests: Native Extension Events
72
+
73
+ #### [NEW] [test_native_events.rb](file:///Users/kerrick/Developer/tokra/test/test_native_events.rb)
74
+
75
+ 1. **IpcEvent**
76
+ - `.new(message)` stores message
77
+ - `#message` returns the stored string
78
+
79
+ 2. **WakeUpEvent**
80
+ - `.new(payload)` stores payload
81
+ - `#payload` returns the stored string
82
+
83
+ 3. **WindowCloseEvent**
84
+ - `.new` creates instance (no args)
85
+
86
+ 4. **HttpRequestEvent**
87
+ - `.new(request_id, method, uri, body)` stores all fields
88
+ - `#request_id`, `#method`, `#uri`, `#body` return correct values
89
+
90
+ 5. **PageLoadEvent**
91
+ - `.new(event, url)` stores both
92
+ - `#event` returns "started" or "finished"
93
+ - `#url` returns the URL
94
+ - `#started?` returns true when event == "started"
95
+ - `#finished?` returns true when event == "finished"
96
+
97
+ ---
98
+
99
+ ### Unit Tests: Native Extension Core Types
100
+
101
+ #### [MODIFY] [thread_safety_invariant_test.rb](file:///Users/kerrick/Developer/tokra/test/integration/thread_safety_invariant_test.rb)
102
+
103
+ Add tests for:
104
+
105
+ 1. **EventLoop**
106
+ - `#create_proxy` returns Proxy before `#run`
107
+ - `#create_proxy` raises after `#run` consumed loop
108
+ - `#run` raises if called twice
109
+
110
+ 2. **Window**
111
+ - `#set_title` accepts string
112
+ - `#set_size` accepts width, height as floats
113
+ - `#id` returns unique identifier
114
+
115
+ 3. **WebView**
116
+ - `.new_with_protocol` registers tokra:// handler
117
+ - `#eval` executes JavaScript
118
+
119
+ 4. **Proxy**
120
+ - `#wake_up` accepts string payload
121
+ - `#respond` accepts request_id, status, headers, body
122
+ - Raises when event loop is closed
123
+
124
+ ---
125
+
126
+ ### Unit Tests: Entry Point & Error Class
127
+
128
+ #### [MODIFY] [test_tokra.rb](file:///Users/kerrick/Developer/tokra/test/test_tokra.rb)
129
+
130
+ 1. **Module structure**
131
+ - `Tokra` is defined
132
+ - `Tokra::Native` is defined
133
+ - `Tokra::Error` is a StandardError subclass
134
+
135
+ 2. **VERSION format**
136
+ - Matches semantic versioning pattern
137
+
138
+ ---
139
+
140
+ ## Verification Plan
141
+
142
+ ### Automated Tests
143
+
144
+ ```bash
145
+ bundle exec rake test
146
+ ```
147
+
148
+ Expected outcome: All tests pass, no skips except display-server-dependent tests in CI.
149
+
150
+ ### Mutation Testing Validation
151
+
152
+ The tests are designed to detect mutations in:
153
+ - Redirect count threshold (MAX_REDIRECTS = 10)
154
+ - Status code range checks ((300..399))
155
+ - env hash key names
156
+ - Method conversion in redirect (POST → GET)
157
+ - Event type predicates (#started?, #finished?)
158
+
159
+ ---
160
+
161
+ ## Execution Order
162
+
163
+ 1. Create `test/test_rack_handler.rb` with mock-based tests
164
+ 2. Create `test/test_native_events.rb` for event types
165
+ 3. Expand `test/integration/thread_safety_invariant_test.rb`
166
+ 4. Update `test/test_tokra.rb` to remove skip
167
+ 5. Run full test suite and verify
168
+
169
+ ## Notes
170
+
171
+ - Tests requiring a display server will skip in CI unless `DISPLAY` is set
172
+ - Rack handler tests will use mock apps and mock proxies to avoid windowing
173
+ - Event type tests can run headless (pure Ruby object instantiation via Rust)
@@ -0,0 +1,111 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+
4
+ SPDX-License-Identifier: CC-BY-SA-4.0
5
+ -->
6
+
7
+ # Implementation Plan: HTTP Header Forwarding for CSRF/Cookie Support
8
+
9
+ ## Problem
10
+
11
+ Rails form submissions fail with 422 CSRF error because cookies are not forwarded from WKWebView to Rails:
12
+
13
+ 1. WKWebView sends requests with `Cookie` header
14
+ 2. Tokra's Rust layer only extracts `method`, `uri`, `body` - **ignoring headers**
15
+ 3. Each Rails request appears as a new session → CSRF token mismatch
16
+
17
+ ## ADR Compatibility
18
+
19
+ | ADR | Finding |
20
+ |-----|---------|
21
+ | **001** | ✅ Compatible. "Rust Layer (The Dumb Pipe)" - we're adding header passthrough, not logic |
22
+ | **002** | ✅ **Explicitly expected**: Line 128 states "we must ensure set-cookie headers are respected by Wry's custom protocol implementation" |
23
+ | **003** | ✅ Compatible. Build system unchanged |
24
+ | **004** | ✅ Compatible. Builder pattern unchanged |
25
+
26
+ ## Tauri Reference
27
+
28
+ Tauri's `protocol/tauri.rs:161` shows:
29
+ ```rust
30
+ for (name, value) in request.headers() {
31
+ proxy_builder = proxy_builder.header(name, value);
32
+ }
33
+ ```
34
+
35
+ Tauri passes the full `http::Request` object (with headers) to handlers.
36
+
37
+ ---
38
+
39
+ ## Proposed Changes
40
+
41
+ ### Rust Layer
42
+
43
+ #### [MODIFY] [user_event.rs](file:///Users/kerrick/Developer/tokra/ext/tokra/src/user_event.rs)
44
+
45
+ Add `headers: Vec<(String, String)>` field to `UserEvent::HttpRequest` variant.
46
+
47
+ #### [MODIFY] [webview.rs](file:///Users/kerrick/Developer/tokra/ext/tokra/src/webview.rs)
48
+
49
+ Extract headers from `request`:
50
+ ```rust
51
+ let headers: Vec<(String, String)> = request.headers()
52
+ .iter()
53
+ .map(|(name, value)| {
54
+ (name.to_string(), value.to_str().unwrap_or("").to_string())
55
+ })
56
+ .collect();
57
+ ```
58
+
59
+ #### [MODIFY] [events.rs](file:///Users/kerrick/Developer/tokra/ext/tokra/src/events.rs)
60
+
61
+ Add `headers` field to `RbHttpRequestEvent`:
62
+ - Store as `Vec<(String, String)>`
63
+ - Expose Ruby method `headers` returning array of 2-tuples
64
+
65
+ #### [MODIFY] [event_loop.rs](file:///Users/kerrick/Developer/tokra/ext/tokra/src/event_loop.rs)
66
+
67
+ Pass `headers` when creating `RbHttpRequestEvent::new()`.
68
+
69
+ ---
70
+
71
+ ### Ruby Layer
72
+
73
+ #### [MODIFY] [handler.rb](file:///Users/kerrick/Developer/tokra/lib/tokra/rack/handler.rb)
74
+
75
+ In `build_env`, convert headers to Rack format:
76
+ ```ruby
77
+ # Add HTTP headers from the request (HTTP_* format for Rack)
78
+ event.headers&.each do |(name, value)|
79
+ rack_name = "HTTP_#{name.upcase.tr('-', '_')}"
80
+ env[rack_name] = value
81
+ end
82
+ ```
83
+
84
+ ---
85
+
86
+ ## Verification Plan
87
+
88
+ ### Automated Tests
89
+
90
+ ```bash
91
+ # Run existing Rust unit tests (should still pass)
92
+ cd /Users/kerrick/Developer/tokra
93
+ cargo test
94
+ ```
95
+
96
+ ### Manual Verification
97
+
98
+ 1. Run the Rails example:
99
+ ```bash
100
+ cd /Users/kerrick/Developer/tokra/examples/verify_rails_sqlite
101
+ timeout 30 ruby tokra.rb 2>&1 || true
102
+ ```
103
+
104
+ 2. In the app window:
105
+ - Click "Add task" to navigate to `/todos/new`
106
+ - Fill in a task title and click "Create"
107
+ - **Expected**: Task is created and displayed (no 422 error)
108
+
109
+ 3. Verify server logs show:
110
+ - `[TOKRA]` logs include `Cookie` header being forwarded
111
+ - Rails logs show successful `POST /todos` with 302/303 redirect
@@ -20,6 +20,19 @@ SPDX-License-Identifier: AGPL-3.0-or-later
20
20
  --text-muted: oklch(50% 0.015 250);
21
21
  --surface: oklch(98% 0.005 250);
22
22
  --accent: oklch(55% 0.18 150);
23
+ --code-bg: oklch(94% 0.01 250);
24
+ --status-bg: oklch(95% 0.04 150);
25
+ }
26
+
27
+ @media (prefers-color-scheme: dark) {
28
+ :root {
29
+ --text: oklch(92% 0.01 250);
30
+ --text-muted: oklch(65% 0.015 250);
31
+ --surface: oklch(15% 0.02 250);
32
+ --accent: oklch(70% 0.18 150);
33
+ --code-bg: oklch(22% 0.02 250);
34
+ --status-bg: oklch(25% 0.06 150);
35
+ }
23
36
  }
24
37
 
25
38
  html {
@@ -60,7 +73,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
60
73
  code {
61
74
  font-family: ui-monospace, 'SF Mono', 'Cascadia Code', monospace;
62
75
  font-size: 0.9em;
63
- background: oklch(94% 0.01 250);
76
+ background: var(--code-bg);
64
77
  padding: 0.15em 0.4em;
65
78
  border-radius: 4px;
66
79
  }
@@ -72,7 +85,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
72
85
  font-size: 0.85rem;
73
86
  font-weight: 600;
74
87
  color: var(--accent);
75
- background: oklch(95% 0.04 150);
88
+ background: var(--status-bg);
76
89
  border-radius: 6px;
77
90
  }
78
91
  </style>
@@ -126,7 +126,9 @@ Rack::Handler::Tokra.run(
126
126
  rack_app,
127
127
  title: "Tokra Ping-Pong",
128
128
  width: 500,
129
- height: 600
129
+ height: 600,
130
+ # This acts like Tauri's invoke_handler, receiving IPC messages
131
+ invoke_handler: proc { |msg| LOGGER.info("Sent via IPC: #{msg}") }
130
132
  )
131
133
 
132
134
  LOGGER.info { "Event loop exited" }
@@ -12,8 +12,35 @@ SPDX-License-Identifier: AGPL-3.0-or-later
12
12
  --border: oklch(88% 0.01 250);
13
13
  --accent: oklch(55% 0.18 150);
14
14
  --accent-bg: oklch(95% 0.04 150);
15
+ --accent-hover: oklch(50% 0.18 150);
15
16
  --warn: oklch(55% 0.16 30);
16
17
  --warn-bg: oklch(95% 0.04 30);
18
+ --input-bg: white;
19
+ --message-bg: oklch(96% 0.008 250);
20
+ --button-hover: oklch(96% 0.005 250);
21
+ --border-hover: oklch(80% 0.01 250);
22
+ --button-active: oklch(94% 0.008 250);
23
+ --pending-bg: oklch(96% 0.005 250);
24
+ }
25
+
26
+ @media (prefers-color-scheme: dark) {
27
+ :root {
28
+ --text: oklch(92% 0.01 250);
29
+ --text-muted: oklch(65% 0.015 250);
30
+ --surface: oklch(15% 0.02 250);
31
+ --border: oklch(32% 0.02 250);
32
+ --accent: oklch(70% 0.18 150);
33
+ --accent-bg: oklch(25% 0.06 150);
34
+ --accent-hover: oklch(65% 0.18 150);
35
+ --warn: oklch(70% 0.16 30);
36
+ --warn-bg: oklch(25% 0.06 30);
37
+ --input-bg: oklch(20% 0.02 250);
38
+ --message-bg: oklch(20% 0.02 250);
39
+ --button-hover: oklch(25% 0.02 250);
40
+ --border-hover: oklch(40% 0.02 250);
41
+ --button-active: oklch(28% 0.02 250);
42
+ --pending-bg: oklch(22% 0.02 250);
43
+ }
17
44
  }
18
45
 
19
46
  html {
@@ -46,7 +73,7 @@ h1 {
46
73
  /* Message from Ruby */
47
74
  .message-block {
48
75
  padding: 1rem 1.25rem;
49
- background: oklch(96% 0.008 250);
76
+ background: var(--message-bg);
50
77
  border-left: 3px solid var(--border);
51
78
  margin-block-end: 2rem;
52
79
  }
@@ -82,7 +109,7 @@ input[type="text"] {
82
109
  font-family: inherit;
83
110
  font-size: 1rem;
84
111
  color: var(--text);
85
- background: white;
112
+ background: var(--input-bg);
86
113
  border: 1px solid var(--border);
87
114
  border-radius: 6px;
88
115
  outline: none;
@@ -106,7 +133,7 @@ button {
106
133
  font-size: 0.875rem;
107
134
  font-weight: 500;
108
135
  color: var(--text);
109
- background: white;
136
+ background: var(--input-bg);
110
137
  border: 1px solid var(--border);
111
138
  border-radius: 6px;
112
139
  cursor: pointer;
@@ -114,12 +141,12 @@ button {
114
141
  }
115
142
 
116
143
  button:hover {
117
- background: oklch(96% 0.005 250);
118
- border-color: oklch(80% 0.01 250);
144
+ background: var(--button-hover);
145
+ border-color: var(--border-hover);
119
146
  }
120
147
 
121
148
  button:active {
122
- background: oklch(94% 0.008 250);
149
+ background: var(--button-active);
123
150
  }
124
151
 
125
152
  button[data-primary] {
@@ -129,8 +156,8 @@ button[data-primary] {
129
156
  }
130
157
 
131
158
  button[data-primary]:hover {
132
- background: oklch(50% 0.18 150);
133
- border-color: oklch(50% 0.18 150);
159
+ background: var(--accent-hover);
160
+ border-color: var(--accent-hover);
134
161
  }
135
162
 
136
163
  /* Status feedback */
@@ -158,7 +185,7 @@ button[data-primary]:hover {
158
185
 
159
186
  .feedback[data-type="pending"] {
160
187
  color: var(--text-muted);
161
- background: oklch(96% 0.005 250);
188
+ background: var(--pending-bg);
162
189
  }
163
190
 
164
191
  /* IPC status indicator */
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
12
12
  <link rel="preconnect" href="https://fonts.googleapis.com">
13
13
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
14
  <link href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600&display=swap" rel="stylesheet">
15
- <link rel="stylesheet" href="tokra://localhost/styles.css">
15
+ <link rel="stylesheet" href="/styles.css">
16
16
  </head>
17
17
  <body>
18
18
  <main>
@@ -0,0 +1,51 @@
1
+ # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.
2
+
3
+ # Ignore git directory.
4
+ /.git/
5
+ /.gitignore
6
+
7
+ # Ignore bundler config.
8
+ /.bundle
9
+
10
+ # Ignore all environment files.
11
+ /.env*
12
+
13
+ # Ignore all default key files.
14
+ /config/master.key
15
+ /config/credentials/*.key
16
+
17
+ # Ignore all logfiles and tempfiles.
18
+ /log/*
19
+ /tmp/*
20
+ !/log/.keep
21
+ !/tmp/.keep
22
+
23
+ # Ignore pidfiles, but keep the directory.
24
+ /tmp/pids/*
25
+ !/tmp/pids/.keep
26
+
27
+ # Ignore storage (uploaded files in development and any SQLite databases).
28
+ /storage/*
29
+ !/storage/.keep
30
+ /tmp/storage/*
31
+ !/tmp/storage/.keep
32
+
33
+ # Ignore assets.
34
+ /node_modules/
35
+ /app/assets/builds/*
36
+ !/app/assets/builds/.keep
37
+ /public/assets
38
+
39
+ # Ignore CI service files.
40
+ /.github
41
+
42
+ # Ignore Kamal files.
43
+ /config/deploy*.yml
44
+ /.kamal
45
+
46
+ # Ignore development files
47
+ /.devcontainer
48
+
49
+ # Ignore Docker-related files
50
+ /.dockerignore
51
+ /Dockerfile*
@@ -0,0 +1,9 @@
1
+ # See https://git-scm.com/docs/gitattributes for more about git attribute files.
2
+
3
+ # Mark the database schema as having been generated.
4
+ db/schema.rb linguist-generated
5
+
6
+ # Mark any vendored files as having been vendored.
7
+ vendor/* linguist-vendored
8
+ config/credentials/*.yml.enc diff=rails_credentials
9
+ config/credentials.yml.enc diff=rails_credentials
@@ -0,0 +1,12 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: weekly
7
+ open-pull-requests-limit: 10
8
+ - package-ecosystem: github-actions
9
+ directory: "/"
10
+ schedule:
11
+ interval: weekly
12
+ open-pull-requests-limit: 10
@@ -0,0 +1,124 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [ main ]
7
+
8
+ jobs:
9
+ scan_ruby:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout code
14
+ uses: actions/checkout@v6
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ bundler-cache: true
20
+
21
+ - name: Scan for common Rails security vulnerabilities using static analysis
22
+ run: bin/brakeman --no-pager
23
+
24
+ - name: Scan for known security vulnerabilities in gems used
25
+ run: bin/bundler-audit
26
+
27
+ scan_js:
28
+ runs-on: ubuntu-latest
29
+
30
+ steps:
31
+ - name: Checkout code
32
+ uses: actions/checkout@v6
33
+
34
+ - name: Set up Ruby
35
+ uses: ruby/setup-ruby@v1
36
+ with:
37
+ bundler-cache: true
38
+
39
+ - name: Scan for security vulnerabilities in JavaScript dependencies
40
+ run: bin/importmap audit
41
+
42
+ lint:
43
+ runs-on: ubuntu-latest
44
+ env:
45
+ RUBOCOP_CACHE_ROOT: tmp/rubocop
46
+ steps:
47
+ - name: Checkout code
48
+ uses: actions/checkout@v6
49
+
50
+ - name: Set up Ruby
51
+ uses: ruby/setup-ruby@v1
52
+ with:
53
+ bundler-cache: true
54
+
55
+ - name: Prepare RuboCop cache
56
+ uses: actions/cache@v4
57
+ env:
58
+ DEPENDENCIES_HASH: ${{ hashFiles('.ruby-version', '**/.rubocop.yml', '**/.rubocop_todo.yml', 'Gemfile.lock') }}
59
+ with:
60
+ path: ${{ env.RUBOCOP_CACHE_ROOT }}
61
+ key: rubocop-${{ runner.os }}-${{ env.DEPENDENCIES_HASH }}-${{ github.ref_name == github.event.repository.default_branch && github.run_id || 'default' }}
62
+ restore-keys: |
63
+ rubocop-${{ runner.os }}-${{ env.DEPENDENCIES_HASH }}-
64
+
65
+ - name: Lint code for consistent style
66
+ run: bin/rubocop -f github
67
+
68
+ test:
69
+ runs-on: ubuntu-latest
70
+
71
+ # services:
72
+ # redis:
73
+ # image: valkey/valkey:8
74
+ # ports:
75
+ # - 6379:6379
76
+ # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
77
+ steps:
78
+ - name: Checkout code
79
+ uses: actions/checkout@v6
80
+
81
+ - name: Set up Ruby
82
+ uses: ruby/setup-ruby@v1
83
+ with:
84
+ bundler-cache: true
85
+
86
+ - name: Run tests
87
+ env:
88
+ RAILS_ENV: test
89
+ # RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
90
+ # REDIS_URL: redis://localhost:6379/0
91
+ run: bin/rails db:test:prepare test
92
+
93
+ system-test:
94
+ runs-on: ubuntu-latest
95
+
96
+ # services:
97
+ # redis:
98
+ # image: valkey/valkey:8
99
+ # ports:
100
+ # - 6379:6379
101
+ # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
102
+ steps:
103
+ - name: Checkout code
104
+ uses: actions/checkout@v6
105
+
106
+ - name: Set up Ruby
107
+ uses: ruby/setup-ruby@v1
108
+ with:
109
+ bundler-cache: true
110
+
111
+ - name: Run System Tests
112
+ env:
113
+ RAILS_ENV: test
114
+ # RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
115
+ # REDIS_URL: redis://localhost:6379/0
116
+ run: bin/rails db:test:prepare test:system
117
+
118
+ - name: Keep screenshots from failed system tests
119
+ uses: actions/upload-artifact@v4
120
+ if: failure()
121
+ with:
122
+ name: screenshots
123
+ path: ${{ github.workspace }}/tmp/screenshots
124
+ if-no-files-found: ignore
@@ -0,0 +1,35 @@
1
+ # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2
+ #
3
+ # Temporary files generated by your text editor or operating system
4
+ # belong in git's global ignore instead:
5
+ # `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore`
6
+
7
+ # Ignore bundler config.
8
+ /.bundle
9
+
10
+ # Ignore all environment files.
11
+ /.env*
12
+
13
+ # Ignore all logfiles and tempfiles.
14
+ /log/*
15
+ /tmp/*
16
+ !/log/.keep
17
+ !/tmp/.keep
18
+
19
+ # Ignore pidfiles, but keep the directory.
20
+ /tmp/pids/*
21
+ !/tmp/pids/
22
+ !/tmp/pids/.keep
23
+
24
+ # Ignore storage (uploaded files in development and any SQLite databases).
25
+ /storage/*
26
+ !/storage/.keep
27
+ /tmp/storage/*
28
+ !/tmp/storage/
29
+ !/tmp/storage/.keep
30
+
31
+ /public/assets
32
+
33
+ # Ignore key files for decrypting credentials and more.
34
+ /config/*.key
35
+
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ echo "Docker set up on $KAMAL_HOSTS..."
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..."
@@ -0,0 +1,14 @@
1
+ #!/bin/sh
2
+
3
+ # A sample post-deploy hook
4
+ #
5
+ # These environment variables are available:
6
+ # KAMAL_RECORDED_AT
7
+ # KAMAL_PERFORMER
8
+ # KAMAL_VERSION
9
+ # KAMAL_HOSTS
10
+ # KAMAL_ROLES (if set)
11
+ # KAMAL_DESTINATION (if set)
12
+ # KAMAL_RUNTIME
13
+
14
+ echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds"