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.
- checksums.yaml +4 -4
- data/LICENSES/MIT-0.txt +16 -0
- data/REUSE.toml +6 -1
- data/Rakefile +2 -0
- data/Steepfile +2 -0
- data/clippy_exceptions.rb +75 -37
- data/doc/contributors/adr/004.md +63 -0
- data/doc/contributors/chats/002.md +177 -0
- data/doc/contributors/chats/003.md +2180 -0
- data/doc/contributors/chats/004.md +1992 -0
- data/doc/contributors/chats/005.md +1529 -0
- data/doc/contributors/plan/002.md +173 -0
- data/doc/contributors/plan/003.md +111 -0
- data/examples/verify_hello_world/index.html +15 -2
- data/examples/verify_ping_pong/app.rb +3 -1
- data/examples/verify_ping_pong/public/styles.css +36 -9
- data/examples/verify_ping_pong/views/layout.erb +1 -1
- data/examples/verify_rails_sqlite/.dockerignore +51 -0
- data/examples/verify_rails_sqlite/.gitattributes +9 -0
- data/examples/verify_rails_sqlite/.github/dependabot.yml +12 -0
- data/examples/verify_rails_sqlite/.github/workflows/ci.yml +124 -0
- data/examples/verify_rails_sqlite/.gitignore +35 -0
- data/examples/verify_rails_sqlite/.kamal/hooks/docker-setup.sample +3 -0
- data/examples/verify_rails_sqlite/.kamal/hooks/post-app-boot.sample +3 -0
- data/examples/verify_rails_sqlite/.kamal/hooks/post-deploy.sample +14 -0
- data/examples/verify_rails_sqlite/.kamal/hooks/post-proxy-reboot.sample +3 -0
- data/examples/verify_rails_sqlite/.kamal/hooks/pre-app-boot.sample +3 -0
- data/examples/verify_rails_sqlite/.kamal/hooks/pre-build.sample +51 -0
- data/examples/verify_rails_sqlite/.kamal/hooks/pre-connect.sample +47 -0
- data/examples/verify_rails_sqlite/.kamal/hooks/pre-deploy.sample +122 -0
- data/examples/verify_rails_sqlite/.kamal/hooks/pre-proxy-reboot.sample +3 -0
- data/examples/verify_rails_sqlite/.kamal/secrets +20 -0
- data/examples/verify_rails_sqlite/.rubocop.yml +2 -0
- data/examples/verify_rails_sqlite/.ruby-version +1 -0
- data/examples/verify_rails_sqlite/Dockerfile +77 -0
- data/examples/verify_rails_sqlite/Gemfile +66 -0
- data/examples/verify_rails_sqlite/Gemfile.lock +563 -0
- data/examples/verify_rails_sqlite/README.md +41 -0
- data/examples/verify_rails_sqlite/Rakefile +6 -0
- data/examples/verify_rails_sqlite/app/assets/images/.keep +0 -0
- data/examples/verify_rails_sqlite/app/assets/stylesheets/application.css +469 -0
- data/examples/verify_rails_sqlite/app/controllers/application_controller.rb +12 -0
- data/examples/verify_rails_sqlite/app/controllers/concerns/.keep +0 -0
- data/examples/verify_rails_sqlite/app/controllers/todos_controller.rb +70 -0
- data/examples/verify_rails_sqlite/app/helpers/application_helper.rb +2 -0
- data/examples/verify_rails_sqlite/app/helpers/todos_helper.rb +2 -0
- data/examples/verify_rails_sqlite/app/javascript/application.js +3 -0
- data/examples/verify_rails_sqlite/app/javascript/controllers/application.js +9 -0
- data/examples/verify_rails_sqlite/app/javascript/controllers/hello_controller.js +7 -0
- data/examples/verify_rails_sqlite/app/javascript/controllers/index.js +4 -0
- data/examples/verify_rails_sqlite/app/jobs/application_job.rb +7 -0
- data/examples/verify_rails_sqlite/app/mailers/application_mailer.rb +4 -0
- data/examples/verify_rails_sqlite/app/models/application_record.rb +3 -0
- data/examples/verify_rails_sqlite/app/models/concerns/.keep +0 -0
- data/examples/verify_rails_sqlite/app/models/todo.rb +10 -0
- data/examples/verify_rails_sqlite/app/views/layouts/application.html.erb +24 -0
- data/examples/verify_rails_sqlite/app/views/layouts/mailer.html.erb +13 -0
- data/examples/verify_rails_sqlite/app/views/layouts/mailer.text.erb +1 -0
- data/examples/verify_rails_sqlite/app/views/pwa/manifest.json.erb +22 -0
- data/examples/verify_rails_sqlite/app/views/pwa/service-worker.js +26 -0
- data/examples/verify_rails_sqlite/app/views/todos/_form.html.erb +27 -0
- data/examples/verify_rails_sqlite/app/views/todos/_todo.html.erb +18 -0
- data/examples/verify_rails_sqlite/app/views/todos/_todo.json.jbuilder +2 -0
- data/examples/verify_rails_sqlite/app/views/todos/edit.html.erb +7 -0
- data/examples/verify_rails_sqlite/app/views/todos/index.html.erb +22 -0
- data/examples/verify_rails_sqlite/app/views/todos/index.json.jbuilder +1 -0
- data/examples/verify_rails_sqlite/app/views/todos/new.html.erb +7 -0
- data/examples/verify_rails_sqlite/app/views/todos/show.html.erb +23 -0
- data/examples/verify_rails_sqlite/app/views/todos/show.json.jbuilder +1 -0
- data/examples/verify_rails_sqlite/bin/brakeman +7 -0
- data/examples/verify_rails_sqlite/bin/bundler-audit +6 -0
- data/examples/verify_rails_sqlite/bin/ci +6 -0
- data/examples/verify_rails_sqlite/bin/dev +2 -0
- data/examples/verify_rails_sqlite/bin/docker-entrypoint +8 -0
- data/examples/verify_rails_sqlite/bin/importmap +4 -0
- data/examples/verify_rails_sqlite/bin/jobs +6 -0
- data/examples/verify_rails_sqlite/bin/kamal +16 -0
- data/examples/verify_rails_sqlite/bin/rails +4 -0
- data/examples/verify_rails_sqlite/bin/rake +4 -0
- data/examples/verify_rails_sqlite/bin/rubocop +8 -0
- data/examples/verify_rails_sqlite/bin/setup +35 -0
- data/examples/verify_rails_sqlite/bin/thrust +5 -0
- data/examples/verify_rails_sqlite/config/application.rb +27 -0
- data/examples/verify_rails_sqlite/config/boot.rb +4 -0
- data/examples/verify_rails_sqlite/config/bundler-audit.yml +5 -0
- data/examples/verify_rails_sqlite/config/cable.yml +17 -0
- data/examples/verify_rails_sqlite/config/cache.yml +16 -0
- data/examples/verify_rails_sqlite/config/ci.rb +24 -0
- data/examples/verify_rails_sqlite/config/credentials.yml.enc +1 -0
- data/examples/verify_rails_sqlite/config/database.yml +40 -0
- data/examples/verify_rails_sqlite/config/deploy.yml +119 -0
- data/examples/verify_rails_sqlite/config/environment.rb +5 -0
- data/examples/verify_rails_sqlite/config/environments/development.rb +84 -0
- data/examples/verify_rails_sqlite/config/environments/production.rb +99 -0
- data/examples/verify_rails_sqlite/config/environments/test.rb +53 -0
- data/examples/verify_rails_sqlite/config/importmap.rb +7 -0
- data/examples/verify_rails_sqlite/config/initializers/assets.rb +7 -0
- data/examples/verify_rails_sqlite/config/initializers/content_security_policy.rb +29 -0
- data/examples/verify_rails_sqlite/config/initializers/filter_parameter_logging.rb +8 -0
- data/examples/verify_rails_sqlite/config/initializers/inflections.rb +16 -0
- data/examples/verify_rails_sqlite/config/locales/en.yml +31 -0
- data/examples/verify_rails_sqlite/config/puma.rb +42 -0
- data/examples/verify_rails_sqlite/config/queue.yml +18 -0
- data/examples/verify_rails_sqlite/config/recurring.yml +15 -0
- data/examples/verify_rails_sqlite/config/routes.rb +15 -0
- data/examples/verify_rails_sqlite/config/storage.yml +27 -0
- data/examples/verify_rails_sqlite/config.ru +6 -0
- data/examples/verify_rails_sqlite/db/cable_schema.rb +11 -0
- data/examples/verify_rails_sqlite/db/cache_schema.rb +12 -0
- data/examples/verify_rails_sqlite/db/migrate/20260130080933_create_todos.rb +10 -0
- data/examples/verify_rails_sqlite/db/queue_schema.rb +129 -0
- data/examples/verify_rails_sqlite/db/schema.rb +20 -0
- data/examples/verify_rails_sqlite/db/seeds.rb +9 -0
- data/examples/verify_rails_sqlite/lib/tasks/.keep +0 -0
- data/examples/verify_rails_sqlite/log/.keep +0 -0
- data/examples/verify_rails_sqlite/public/400.html +135 -0
- data/examples/verify_rails_sqlite/public/404.html +135 -0
- data/examples/verify_rails_sqlite/public/406-unsupported-browser.html +135 -0
- data/examples/verify_rails_sqlite/public/422.html +135 -0
- data/examples/verify_rails_sqlite/public/500.html +135 -0
- data/examples/verify_rails_sqlite/public/icon.png +0 -0
- data/examples/verify_rails_sqlite/public/icon.svg +3 -0
- data/examples/verify_rails_sqlite/public/robots.txt +1 -0
- data/examples/verify_rails_sqlite/public/styles.css +469 -0
- data/examples/verify_rails_sqlite/script/.keep +0 -0
- data/examples/verify_rails_sqlite/storage/.keep +0 -0
- data/examples/verify_rails_sqlite/test/controllers/.keep +0 -0
- data/examples/verify_rails_sqlite/test/controllers/todos_controller_test.rb +48 -0
- data/examples/verify_rails_sqlite/test/fixtures/files/.keep +0 -0
- data/examples/verify_rails_sqlite/test/fixtures/todos.yml +9 -0
- data/examples/verify_rails_sqlite/test/helpers/.keep +0 -0
- data/examples/verify_rails_sqlite/test/integration/.keep +0 -0
- data/examples/verify_rails_sqlite/test/mailers/.keep +0 -0
- data/examples/verify_rails_sqlite/test/models/.keep +0 -0
- data/examples/verify_rails_sqlite/test/models/todo_test.rb +7 -0
- data/examples/verify_rails_sqlite/test/test_helper.rb +15 -0
- data/examples/verify_rails_sqlite/tmp/.keep +0 -0
- data/examples/verify_rails_sqlite/tmp/pids/.keep +0 -0
- data/examples/verify_rails_sqlite/tmp/storage/.keep +0 -0
- data/examples/verify_rails_sqlite/tokra.rb +42 -0
- data/examples/verify_rails_sqlite/vendor/.keep +0 -0
- data/examples/verify_rails_sqlite/vendor/javascript/.keep +0 -0
- data/ext/tokra/src/event_loop.rs +206 -0
- data/ext/tokra/src/events.rs +430 -0
- data/ext/tokra/src/lib.rs +52 -664
- data/ext/tokra/src/proxy.rs +142 -0
- data/ext/tokra/src/responders.rs +86 -0
- data/ext/tokra/src/user_event.rs +313 -0
- data/ext/tokra/src/webview.rs +194 -0
- data/ext/tokra/src/window.rs +92 -0
- data/lib/tokra/rack/handler.rb +37 -14
- data/lib/tokra/version.rb +1 -1
- data/rbs_exceptions.rb +12 -0
- data/sig/tokra.rbs +95 -1
- data/tasks/lint.rake +2 -2
- data/tasks/lint.rb +49 -0
- data/tasks/rust.rake +6 -23
- data/tasks/steep.rake +25 -3
- data/tasks/test.rake +1 -1
- 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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
118
|
-
border-color:
|
|
144
|
+
background: var(--button-hover);
|
|
145
|
+
border-color: var(--border-hover);
|
|
119
146
|
}
|
|
120
147
|
|
|
121
148
|
button:active {
|
|
122
|
-
background:
|
|
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:
|
|
133
|
-
border-color:
|
|
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:
|
|
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="
|
|
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,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,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"
|