parse-stack-next 5.5.4 → 5.5.6

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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +121 -6
  3. data/README.md +26 -13
  4. data/bin/parse-console +9 -1
  5. data/docs/TEST_SERVER.md +115 -238
  6. data/docs/mcp_guide.md +1 -1
  7. data/docs/mongodb_index_optimization_guide.md +3 -2
  8. data/docs/usage_guide.md +1 -1
  9. data/docs/yard-template/default/fulldoc/html/css/common.css +52 -9
  10. data/docs/yard-template/default/fulldoc/html/css/full_list.css +40 -13
  11. data/lib/parse/agent/constraint_translator.rb +18 -18
  12. data/lib/parse/agent/errors.rb +29 -7
  13. data/lib/parse/agent/mcp_rack_app.rb +29 -11
  14. data/lib/parse/agent/metadata_dsl.rb +6 -6
  15. data/lib/parse/agent/tools.rb +250 -59
  16. data/lib/parse/agent.rb +42 -30
  17. data/lib/parse/api/aggregate.rb +3 -3
  18. data/lib/parse/api/cloud_functions.rb +19 -10
  19. data/lib/parse/api/objects.rb +8 -8
  20. data/lib/parse/api/users.rb +9 -9
  21. data/lib/parse/atlas_search/session.rb +34 -34
  22. data/lib/parse/atlas_search.rb +243 -110
  23. data/lib/parse/client/body_builder.rb +10 -10
  24. data/lib/parse/client/logging.rb +5 -2
  25. data/lib/parse/client/profiling.rb +5 -2
  26. data/lib/parse/client/protocol.rb +1 -1
  27. data/lib/parse/client/url_redaction.rb +94 -0
  28. data/lib/parse/client.rb +43 -28
  29. data/lib/parse/embeddings/image_fetch.rb +6 -1
  30. data/lib/parse/embeddings/voyage.rb +16 -17
  31. data/lib/parse/live_query/client.rb +7 -7
  32. data/lib/parse/live_query/subscription.rb +1 -1
  33. data/lib/parse/lock.rb +1 -1
  34. data/lib/parse/lock_backend.rb +118 -2
  35. data/lib/parse/model/acl.rb +24 -24
  36. data/lib/parse/model/classes/job_schedule.rb +8 -8
  37. data/lib/parse/model/classes/job_status.rb +9 -9
  38. data/lib/parse/model/classes/role.rb +49 -49
  39. data/lib/parse/model/classes/session.rb +2 -2
  40. data/lib/parse/model/classes/user.rb +66 -66
  41. data/lib/parse/model/core/builder.rb +7 -7
  42. data/lib/parse/model/core/create_lock.rb +1 -1
  43. data/lib/parse/model/core/properties.rb +4 -4
  44. data/lib/parse/model/file.rb +57 -16
  45. data/lib/parse/model/model.rb +19 -19
  46. data/lib/parse/model/object.rb +38 -38
  47. data/lib/parse/model/pointer.rb +4 -4
  48. data/lib/parse/model/push.rb +5 -5
  49. data/lib/parse/mongodb.rb +84 -26
  50. data/lib/parse/pipeline_security.rb +2 -2
  51. data/lib/parse/query/constraints.rb +38 -38
  52. data/lib/parse/query.rb +151 -75
  53. data/lib/parse/retrieval/reranker/cohere.rb +30 -0
  54. data/lib/parse/schema.rb +1 -1
  55. data/lib/parse/stack/version.rb +1 -1
  56. data/lib/parse/stack.rb +23 -10
  57. data/lib/parse/two_factor_auth/user_extension.rb +25 -25
  58. data/lib/parse/webhooks/payload.rb +35 -35
  59. data/lib/parse/webhooks/registration.rb +2 -2
  60. data/lib/parse/webhooks/replay_protection.rb +16 -16
  61. data/lib/parse/webhooks.rb +11 -11
  62. data/parse-stack-next.gemspec +19 -1
  63. metadata +2 -38
  64. data/.bundle/config +0 -5
  65. data/.env.sample +0 -138
  66. data/.env.test +0 -10
  67. data/.github/ISSUE_TEMPLATE/bug_report.yml +0 -105
  68. data/.github/ISSUE_TEMPLATE/feature_request.yml +0 -67
  69. data/.github/dependabot.yml +0 -13
  70. data/.github/workflows/codeql.yml +0 -44
  71. data/.github/workflows/docs.yml +0 -39
  72. data/.github/workflows/release.yml +0 -43
  73. data/.github/workflows/ruby.yml +0 -38
  74. data/.gitignore +0 -56
  75. data/.ruby-version +0 -1
  76. data/.solargraph.yml +0 -22
  77. data/.vscode/settings.json +0 -3
  78. data/.yardopts +0 -19
  79. data/Gemfile +0 -43
  80. data/Gemfile.lock +0 -198
  81. data/Makefile +0 -63
  82. data/Rakefile +0 -825
  83. data/config/parse-config.json +0 -12
  84. data/scripts/debug-ips.js +0 -35
  85. data/scripts/docker/Dockerfile.parse +0 -17
  86. data/scripts/docker/atlas-init.js +0 -284
  87. data/scripts/docker/docker-compose.atlas.yml +0 -80
  88. data/scripts/docker/docker-compose.test.yml +0 -159
  89. data/scripts/docker/docker-compose.verifyemail.yml +0 -4
  90. data/scripts/docker/mongo-init.js +0 -21
  91. data/scripts/docker/preflight.sh +0 -76
  92. data/scripts/eval_mcp_with_lm_studio.rb +0 -274
  93. data/scripts/start-parse.sh +0 -154
  94. data/scripts/start_mcp_server.rb +0 -78
  95. data/scripts/test_server_connection.rb +0 -82
  96. data/scripts/vector_prototype/create_vector_index.js +0 -105
  97. data/scripts/vector_prototype/fetch_embeddings.py +0 -241
  98. data/scripts/vector_prototype/fixture_manifest.json +0 -9
  99. data/scripts/vector_prototype/query_prototype.rb +0 -84
  100. data/scripts/vector_prototype/run.sh +0 -34
data/docs/TEST_SERVER.md CHANGED
@@ -1,271 +1,148 @@
1
1
  # Parse Stack Test Server Setup
2
2
 
3
- This document explains how to set up a local Parse Server for testing the parse-stack Ruby SDK.
3
+ This document explains how to run the local, isolated Parse Server stack used
4
+ to exercise the parse-stack-next Ruby SDK's integration suite.
5
+
6
+ The stack is namespaced so it never collides with another Parse test system on
7
+ the same machine: a dedicated Compose project, a private `29xxx` host-port
8
+ block, dedicated container/volume names (`psnext-it-*`), loopback-only port
9
+ binds by default, and a dedicated database.
4
10
 
5
11
  ## Quick Start
6
12
 
7
- ### Option 1: Using Make (Recommended)
13
+ ### Option 1: Using Make
8
14
 
9
15
  ```bash
10
- # Start the test server
11
- make test-server-start
12
-
13
- # Test the connection
14
- make test-connection
15
-
16
- # Run integration tests
17
- make test-integration
18
-
19
- # Stop the test server
20
- make test-server-stop
16
+ make test-server-start # bring up the stack
17
+ make test-connection # verify connectivity
18
+ make test-integration # run the integration suite
19
+ make test-server-stop # tear the stack down
21
20
  ```
22
21
 
23
- ### Option 2: Docker Compose
22
+ ### Option 2: Docker Compose directly
24
23
 
25
- 1. **Start the test server:**
26
- ```bash
27
- docker-compose -f docker-compose.test.yml up -d
28
- ```
24
+ ```bash
25
+ # Start
26
+ docker-compose -f scripts/docker/docker-compose.test.yml up -d
29
27
 
30
- 2. **Test the connection:**
31
- ```bash
32
- ruby test_server_connection.rb
33
- ```
28
+ # Verify Parse Server is healthy
29
+ curl http://localhost:29337/parse/health # => {"status":"ok"}
34
30
 
35
- 3. **Run integration tests:**
36
- ```bash
37
- PARSE_TEST_USE_DOCKER=true bundle exec rake test
38
- ```
31
+ # Run integration tests
32
+ PARSE_TEST_USE_DOCKER=true bundle exec rake test:integration
39
33
 
40
- 4. **Stop the test server:**
41
- ```bash
42
- docker-compose -f docker-compose.test.yml down
43
- ```
34
+ # Stop
35
+ docker-compose -f scripts/docker/docker-compose.test.yml down
36
+ ```
44
37
 
45
- ### Option 3: Use Your Own Parse Server
38
+ ### Option 3: Use your own Parse Server
46
39
 
47
- Set environment variables to point to your Parse Server:
40
+ Point the suite at any Parse Server by exporting the client-side variables:
48
41
 
49
42
  ```bash
50
43
  export PARSE_TEST_SERVER_URL="http://your-server:1337/parse"
51
44
  export PARSE_TEST_APP_ID="your-app-id"
52
- export PARSE_TEST_API_KEY="your-rest-key"
45
+ export PARSE_TEST_API_KEY="your-rest-key"
53
46
  export PARSE_TEST_MASTER_KEY="your-master-key"
54
47
  ```
55
48
 
56
- ## Services Included
57
-
58
- The Docker Compose setup provides:
59
-
60
- - **MongoDB** (port 27017): Database backend
61
- - **Parse Server** (port 1337): Main API server with custom startup script
62
- - **Parse Dashboard** (port 4040): Web interface for data management
63
-
64
- ## Technical Implementation
65
-
66
- ### Custom Parse Server Image
67
-
68
- The setup uses a custom Docker image built on top of `parseplatform/parse-server:8.2.3` that includes:
69
-
70
- - **Custom startup script** (`scripts/start-parse.sh`) that sets the `PARSE_SERVER_MASTER_KEY_IPS` environment variable
71
- - **IP restriction bypass** allowing requests from any IP address (`0.0.0.0/0,::/0`)
72
- - **Automatic environment variable setup** for proper master key authentication
73
-
74
- ### Master Key Authentication
75
-
76
- The setup resolves Parse Server's IP restriction for master key usage by:
77
-
78
- 1. Using a custom Docker image with an embedded startup script
79
- 2. Setting `PARSE_SERVER_MASTER_KEY_IPS=0.0.0.0/0,::/0` to allow all IP addresses
80
- 3. This enables schema operations and full master key functionality for testing
81
-
82
- ### File Structure
83
-
84
- ```
85
- parse-stack-next/
86
- ├── scripts/
87
- │ ├── docker/
88
- │ │ ├── docker-compose.test.yml # Main Docker Compose configuration
89
- │ │ └── Dockerfile.parse # Custom Parse Server image
90
- │ ├── start-parse.sh # Startup script with environment setup
91
- │ └── test_server_connection.rb # Connection test script
92
- ├── config/
93
- │ └── parse-config.json # Parse Server configuration (unused)
94
- ├── test/
95
- │ ├── cloud/
96
- │ │ └── main.js # Cloud Code for testing
97
- │ └── support/
98
- │ ├── test_server.rb # Ruby test helper utilities
99
- │ └── docker_helper.rb # Docker container management
100
- └── .env.test # Environment variable defaults
101
- ```
102
-
103
- ## Test Configuration
104
-
105
- ### Environment Variables
106
-
107
- | Variable | Default | Description |
108
- |----------|---------|-------------|
109
- | `PARSE_TEST_SERVER_URL` | `http://localhost:1337/parse` | Parse Server URL |
110
- | `PARSE_TEST_APP_ID` | `myAppId` | Application ID |
111
- | `PARSE_TEST_API_KEY` | `test-rest-key` | REST API Key |
112
- | `PARSE_TEST_MASTER_KEY` | `myMasterKey` | Master Key |
113
- | `PARSE_TEST_USE_DOCKER` | `false` | Auto-manage Docker containers |
114
- | `PARSE_TEST_AUTO_START` | `false` | Start containers automatically |
115
- | `PARSE_TEST_AUTO_STOP` | `false` | Stop containers on exit |
116
-
117
- ### Using `.env.test`
118
-
119
- Copy and customize the test environment file:
120
-
121
- ```bash
122
- cp .env.test .env.test.local
123
- # Edit .env.test.local with your settings
124
- ```
125
-
126
- ## Writing Integration Tests
127
-
128
- ### Basic Setup
129
-
130
- ```ruby
131
- require_relative 'test_helper_integration'
132
-
133
- class MyIntegrationTest < Minitest::Test
134
- include ParseStackIntegrationTest
135
-
136
- def test_user_creation
137
- with_parse_server do
138
- user = create_test_user(username: 'testuser')
139
- assert user.id.present?
140
- assert_equal 'testuser', user.username
141
- end
142
- end
143
- end
144
- ```
145
-
146
- ### Test Helpers Available
147
-
148
- - `with_parse_server { }` - Skip test if server unavailable
149
- - `create_test_user(attributes)` - Create and track test user
150
- - `create_test_object(class_name, attributes)` - Create and track test object
151
- - `reset_database!` - Clear all non-system data
152
- - `@test_context.track(object)` - Track object for cleanup
153
-
154
- ### Manual Server Management
155
-
156
- ```ruby
157
- # In your tests or console
158
- require 'test/support/docker_helper'
159
-
160
- # Start containers
161
- Parse::Test::DockerHelper.start!
162
-
163
- # Check if running
164
- Parse::Test::DockerHelper.running?
165
-
166
- # View logs
167
- puts Parse::Test::DockerHelper.logs
168
-
169
- # Stop containers
170
- Parse::Test::DockerHelper.stop!
171
- ```
172
-
173
- ## Dashboard Access
174
-
175
- When using Docker Compose, you can access the Parse Dashboard at:
176
- - URL: http://localhost:4040
177
- - Username: `admin`
178
- - Password: `admin`
179
-
180
- ## Cloud Code Testing
181
-
182
- Sample cloud functions are provided in `test/cloud/main.js`:
183
-
184
- ```ruby
185
- # Test cloud functions
186
- result = Parse::CloudFunction.call('hello', name: 'World')
187
- assert_equal 'Hello World!', result
188
- ```
49
+ ## Services and ports
50
+
51
+ Every value has a baked-in default, so the stack is isolated even with no env
52
+ file present. Host ports live in the `29xxx` block and bind to `127.0.0.1` by
53
+ default.
54
+
55
+ | Service | Host port | Override | Container |
56
+ |-----------------|-----------|---------------------|-----------------------|
57
+ | Parse Server | 29337 | `PARSE_HOST_PORT` | `psnext-it-server` |
58
+ | MongoDB | 29017 | `MONGO_HOST_PORT` | `psnext-it-mongo` |
59
+ | Redis | 29379 | `REDIS_HOST_PORT` | `psnext-it-redis` |
60
+ | Parse Dashboard | 29040 | `DASHBOARD_HOST_PORT` | `psnext-it-dashboard` |
61
+
62
+ - **`PSNEXT_PREFIX`** (default `psnext-it`) names the Compose project and every
63
+ container. Set it (e.g. `PSNEXT_PREFIX=psnext-ci`) to run a second, fully
64
+ separate copy.
65
+ - **Versions**: Parse Server is pinned to `parseplatform/parse-server:9.9.0`
66
+ (see `scripts/docker/Dockerfile.parse`), MongoDB `mongo:8`, Redis
67
+ `redis:7-alpine`, Dashboard `parseplatform/parse-dashboard:9`.
68
+ - **Database**: Parse uses `parse_stack_next_it`.
69
+
70
+ ## Credentials (compose defaults)
71
+
72
+ | Setting | Default | Compose env |
73
+ |-------------|---------------------|-------------------------|
74
+ | App ID | `psnextItAppId` | `PARSE_APP_ID` |
75
+ | Master key | `psnextItMasterKey` | `PARSE_MASTER_KEY` |
76
+ | REST key | `psnext-it-rest-key`| `PARSE_API_KEY` |
77
+
78
+ These defaults are intentionally non-secret — they only ever bind to loopback.
79
+ Supply real values (and non-loopback binds) via your shell or a secret manager
80
+ if you point the stack at anything shared.
81
+
82
+ ## Security posture (not an anti-pattern)
83
+
84
+ The stack is hardened by construction, not opened up:
85
+
86
+ - **Loopback binds by default** — `PARSE_BIND` / `MONGO_BIND` / `REDIS_BIND` /
87
+ `DASHBOARD_BIND` all default to `127.0.0.1`, so nothing is published on the
88
+ LAN unless you explicitly override a bind.
89
+ - **Scoped master-key IPs** Parse Server's `masterKeyIps` is set to loopback
90
+ plus the private Docker ranges
91
+ (`127.0.0.1/32,::1/128,172.16.0.0/12,192.168.0.0/16,10.0.0.0/8`) so the
92
+ Ruby suite and the Dashboard container can use the master key, but it is
93
+ **not** opened to `0.0.0.0/0`.
94
+ - **Preflight guard** — a `preflight` service (`scripts/docker/preflight.sh`)
95
+ gates startup and refuses to bring the stack up on a non-loopback bind while
96
+ still using the default credentials, unless `ALLOW_INSECURE_BIND=1` or real
97
+ `PARSE_MASTER_KEY` / `MONGO_ROOT_PASSWORD` values are supplied.
98
+
99
+ ## Client-side test variables
100
+
101
+ The Ruby suite reads these (all have `29xxx` / `psnext-it` defaults):
102
+
103
+ | Variable | Purpose |
104
+ |--------------------------|--------------------------------------|
105
+ | `PARSE_TEST_SERVER_URL` | Parse Server URL (`http://localhost:29337/parse`) |
106
+ | `PARSE_TEST_APP_ID` | Application ID |
107
+ | `PARSE_TEST_API_KEY` | REST API key |
108
+ | `PARSE_TEST_MASTER_KEY` | Master key |
109
+ | `PARSE_TEST_MONGO_URI` | Mongo URI for mongo-direct tests |
110
+ | `PARSE_TEST_REDIS_URL` | Redis URL |
111
+ | `PARSE_TEST_LIVE_QUERY_URL` | LiveQuery WebSocket URL |
112
+ | `PARSE_TEST_USE_DOCKER` | Auto-manage the Docker stack |
113
+
114
+ `.env.test` is a committed reference listing the whole set;
115
+ `set -a; source .env.test; set +a` loads them all at once. Nothing auto-loads
116
+ it the baked-in defaults apply otherwise.
117
+
118
+ ## Dashboard
119
+
120
+ With the stack up, the Parse Dashboard is at <http://localhost:29040>.
189
121
 
190
122
  ## Troubleshooting
191
123
 
192
- ### Docker Issues
193
-
194
124
  ```bash
195
- # Check container status
196
- docker-compose -f docker-compose.test.yml ps
197
-
198
- # View Parse Server logs
199
- docker logs parse-stack-test-server
200
-
201
- # Reset everything
202
- docker-compose -f docker-compose.test.yml down -v
203
- docker-compose -f docker-compose.test.yml up -d
204
- ```
205
-
206
- ### Master Key Authentication Issues
207
-
208
- If you see `Request using master key rejected as the request IP address ... is not set in Parse Server option 'masterKeyIps'`:
209
-
210
- 1. **Verify the custom image is built**:
211
- ```bash
212
- docker-compose -f docker-compose.test.yml build parse
213
- ```
214
-
215
- 2. **Check startup script execution**:
216
- ```bash
217
- docker logs parse-stack-test-server | grep "PARSE_SERVER_MASTER_KEY_IPS"
218
- ```
219
- Should show: `PARSE_SERVER_MASTER_KEY_IPS: 0.0.0.0/0,::/0`
125
+ # Container status
126
+ docker-compose -f scripts/docker/docker-compose.test.yml ps
220
127
 
221
- 3. **Test master key directly**:
222
- ```bash
223
- curl -X GET \
224
- -H "X-Parse-Application-Id: myAppId" \
225
- -H "X-Parse-Master-Key: myMasterKey" \
226
- http://localhost:1337/parse/schemas
227
- ```
128
+ # Parse Server logs
129
+ docker logs psnext-it-server
228
130
 
229
- ### Connection Issues
230
-
231
- ```ruby
232
- # Test connectivity in console
233
- require 'test/support/test_server'
234
- Parse::Test::ServerHelper.setup
235
- Parse::Test::ServerHelper.server_available?
236
- ```
237
-
238
- ### Port Conflicts
239
-
240
- If ports 1337, 4040, or 27017 are in use, modify `docker-compose.test.yml`:
241
-
242
- ```yaml
243
- services:
244
- parse:
245
- ports:
246
- - "1338:1337" # Use port 1338 instead
131
+ # Full reset (clears volumes)
132
+ docker-compose -f scripts/docker/docker-compose.test.yml down -v
133
+ docker-compose -f scripts/docker/docker-compose.test.yml up -d
247
134
  ```
248
135
 
249
- Then update your environment variables accordingly.
250
-
251
- ## Production vs Test Differences
252
-
253
- The test server configuration includes:
254
- - Relaxed security settings for testing
255
- - Auto-creation of classes
256
- - Verbose logging
257
- - Sample cloud code
258
-
259
- **Never use these settings in production!**
136
+ ### Master-key IP rejection
260
137
 
261
- ## Status
138
+ If you see `Request using master key rejected as the request IP address ... is
139
+ not set in Parse Server option 'masterKeyIps'`, your client is reaching the
140
+ server from an address outside the scoped `masterKeyIps` list above. Run the
141
+ suite from the host (loopback) or from within the Docker network, rather than
142
+ widening `masterKeyIps`.
262
143
 
263
- **Working Setup**: This test server configuration has been verified to work with:
264
- - Parse Server 8.2.3
265
- - MongoDB 5.0
266
- - Master key authentication for schema operations
267
- - Basic CRUD operations via REST API
268
- - Ruby Parse Stack SDK connection
269
- - Cloud Code execution
144
+ ### Port conflicts
270
145
 
271
- The setup successfully resolves Parse Server's IP restriction issues that typically prevent master key usage in Docker environments.
146
+ Every host port is overridable via the `*_HOST_PORT` variables in the table
147
+ above (and the matching `PARSE_TEST_*` client variable). Move both the compose
148
+ side and the client side together so the containers and the suite agree.
data/docs/mcp_guide.md CHANGED
@@ -3844,7 +3844,7 @@ Tenant scope is applied as a `$match` stage at the TOP-level pipeline only. Sub-
3844
3844
  2. **Refuse lookups from tenant-bound agents entirely** — application code rejects pipelines containing `$lookup` / `$graphLookup` / `$unionWith` when `agent.tenant_id` is set.
3845
3845
  3. **Mark joinable cross-tenant classes as `agent_hidden`** — the most permissive joining-class is unreachable to the agent.
3846
3846
 
3847
- The proper fix (recursive scope injection into sub-pipelines) is tracked as a follow-up; see [SECURITY_GUIDE.md](../SECURITY_GUIDE.md) for the threat model and posture recommendations.
3847
+ The proper fix (recursive scope injection into sub-pipelines) is tracked as a follow-up; see [acl_clp_guide.md](./acl_clp_guide.md) and [SECURITY.md](../SECURITY.md) for the threat model and posture recommendations.
3848
3848
 
3849
3849
  ---
3850
3850
 
@@ -645,8 +645,9 @@ explicit mappings are correct for production.
645
645
  - [mongodb_direct_guide.md](./mongodb_direct_guide.md) — the full
646
646
  direct-Mongo / index-management API reference (DSL spelling,
647
647
  writer URI, triple-gate, rake tasks)
648
- - [SECURITY_GUIDE.md](../SECURITY_GUIDE.md) — security posture around
649
- the writer URI, role validation, audit trail
648
+ - [acl_clp_guide.md](./acl_clp_guide.md) — security posture around
649
+ the writer URI, role validation, and ACL/CLP enforcement on the
650
+ mongo-direct path
650
651
  - MongoDB official: <https://www.mongodb.com/docs/manual/indexes/>
651
652
  - Parse Server source for auto-managed indexes:
652
653
  <https://github.com/parse-community/parse-server/blob/master/src/Adapters/Storage/Mongo/MongoStorageAdapter.js>
data/docs/usage_guide.md CHANGED
@@ -626,6 +626,6 @@ end
626
626
 
627
627
  ## More Information
628
628
 
629
- - [CHANGELOG](./CHANGELOG.md) - Full feature history
629
+ - [CHANGELOG](../CHANGELOG.md) - Full feature history
630
630
  - [GitHub Releases](https://github.com/neurosynq/parse-stack-next/releases) - Release notes
631
631
  - [Parse Server Docs](https://docs.parseplatform.org) - Parse Server documentation
@@ -438,17 +438,38 @@ input[name=q]::placeholder {
438
438
  margin-top: 1.4em;
439
439
  }
440
440
 
441
- #listing ul.alpha li.letter h2 {
441
+ /* YARD emits the section letters as bare `<li class="letter">A</li>`
442
+ * (no inner h2), so style the li itself. */
443
+ #listing ul.alpha li.letter {
442
444
  color: var(--accent);
443
- border-bottom: 2px solid var(--accent);
444
- display: inline-block;
445
- padding: 0 .8em .2em 0;
445
+ font-weight: 650;
446
446
  }
447
447
 
448
448
  #listing ul li a {
449
449
  border-bottom: 1px dotted var(--border);
450
450
  }
451
451
 
452
+ /* The "(Parse::Constraint)" origin annotations; stock #666 is too dim
453
+ * against the dark surfaces. */
454
+ #listing ul small {
455
+ color: var(--fg-muted);
456
+ }
457
+
458
+ /* Stock style.css stripes index rows with hardcoded light grays
459
+ * (li.r1 #f0f0f0 / li.r2 #fafafa) that glow white in dark mode. */
460
+ li.r1 { background: var(--bg-soft); }
461
+ li.r2 { background: var(--bg); }
462
+
463
+ /* File Listing pills at the top of _index.html. */
464
+ .index_inline_list li,
465
+ .index_inline_list li.r1,
466
+ .index_inline_list li.r2 {
467
+ background: var(--bg-soft);
468
+ border: 1px solid var(--border);
469
+ border-radius: var(--radius-sm);
470
+ margin-right: 6px;
471
+ }
472
+
452
473
  /* -------- inheritance / mixin lists -------- */
453
474
 
454
475
  dl.box {
@@ -892,9 +913,13 @@ h2.h2_sum,
892
913
 
893
914
  /* The collapse arrows in the class-tree iframe are a base64 PNG
894
915
  * sprite of dark glyphs on a transparent background — invisible
895
- * against our dark surface. CSS filter inverts them in place. */
896
- #full_list li a.toggle {
897
- filter: invert(0.85) hue-rotate(180deg) brightness(1.6);
916
+ * against our dark surface. CSS filter inverts them in place.
917
+ * Dark mode only: in light mode the stock dark glyphs are correct,
918
+ * and inverting them would wash them out against the light rail. */
919
+ @media (prefers-color-scheme: dark) {
920
+ #full_list li a.toggle {
921
+ filter: invert(0.85) hue-rotate(180deg) brightness(1.6);
922
+ }
898
923
  }
899
924
 
900
925
  /* ============================================================
@@ -928,11 +953,29 @@ h2.h2_sum,
928
953
  padding: 1px 6px !important;
929
954
  }
930
955
 
931
- /* But never on the code blocks themselves (the pre.code descendants). */
956
+ /* But never on the code blocks themselves (the pre.code descendants).
957
+ * The scoped variants are required, not belt-and-braces: file pages
958
+ * (README + guides) compile fenced blocks to <pre><code class="ruby">,
959
+ * and with both rules !important the positive `#filecontents code`
960
+ * (0,1,0,1) would otherwise beat a bare `pre code` (0,0,0,2) — the
961
+ * inline-level <code> then draws its border on every wrapped line
962
+ * fragment, streaking the whole block. */
932
963
  pre code,
933
964
  pre tt,
934
965
  pre.code code,
935
- pre.code tt {
966
+ pre.code tt,
967
+ .docstring pre code,
968
+ .docstring pre tt,
969
+ .tags pre code,
970
+ .tags pre tt,
971
+ .summary pre code,
972
+ .summary pre tt,
973
+ #filecontents pre code,
974
+ #filecontents pre tt,
975
+ .discussion pre code,
976
+ .discussion pre tt,
977
+ .method_details pre code,
978
+ .method_details pre tt {
936
979
  background: transparent !important;
937
980
  border: 0 !important;
938
981
  padding: 0 !important;
@@ -209,19 +209,43 @@ a:visited {
209
209
  * parse-stack-next overlay (sidebar / class-list iframe)
210
210
  * ============================================================ */
211
211
 
212
+ /* Scheme-aware palette. Light is the default; the dark block below
213
+ * mirrors common.css's prefers-color-scheme handling. Keeping the
214
+ * overlay unconditional (as it originally was) made the rail
215
+ * unreadable in light mode: common.css paints #full_list with the
216
+ * light surface while these rules kept the near-white dark-mode
217
+ * text (its `#full_list li .item a` selector out-specifies
218
+ * common.css's `#full_list li a` regardless of load order). */
212
219
  :root {
213
- --ps-bg: #1E1F22;
214
- --ps-bg-soft: #2A2C30;
215
- --ps-bg-hover: #34373C;
216
- --ps-fg: #E6E8EB;
217
- --ps-fg-muted: #9CA3AF;
218
- --ps-accent: #E63946;
219
- --ps-link: #4FB8F4;
220
- --ps-border: #3A3D42;
220
+ --ps-bg: #FFFFFF;
221
+ --ps-bg-soft: #F8F9FA;
222
+ --ps-bg-hover: #E8EAED;
223
+ --ps-fg: #1F2328;
224
+ --ps-fg-muted: #6B7280;
225
+ --ps-accent: #B22234;
226
+ --ps-link: #169CEE;
227
+ --ps-border: #E5E7EB;
228
+ --ps-scroll-thumb: #C7CBD1;
229
+ --ps-header-shadow: 0 1px 3px rgba(0, 0, 0, .08);
221
230
  --ps-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Inter",
222
231
  Roboto, "Helvetica Neue", Arial, sans-serif;
223
232
  }
224
233
 
234
+ @media (prefers-color-scheme: dark) {
235
+ :root {
236
+ --ps-bg: #1E1F22;
237
+ --ps-bg-soft: #2A2C30;
238
+ --ps-bg-hover: #34373C;
239
+ --ps-fg: #E6E8EB;
240
+ --ps-fg-muted: #9CA3AF;
241
+ --ps-accent: #E63946;
242
+ --ps-link: #4FB8F4;
243
+ --ps-border: #3A3D42;
244
+ --ps-scroll-thumb: #4a4d52;
245
+ --ps-header-shadow: 0 1px 3px rgba(0, 0, 0, .3);
246
+ }
247
+ }
248
+
225
249
  body {
226
250
  background: var(--ps-bg) !important;
227
251
  color: var(--ps-fg);
@@ -238,7 +262,7 @@ h1 {
238
262
  .fixed_header {
239
263
  background: var(--ps-bg-soft) !important;
240
264
  border-bottom: 1px solid var(--ps-border);
241
- box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
265
+ box-shadow: var(--ps-header-shadow);
242
266
  }
243
267
 
244
268
  #search input,
@@ -299,7 +323,7 @@ h1 {
299
323
 
300
324
  ::-webkit-scrollbar { width: 8px; height: 8px; }
301
325
  ::-webkit-scrollbar-track { background: var(--ps-bg); }
302
- ::-webkit-scrollbar-thumb { background: #4a4d52; border-radius: 4px; }
326
+ ::-webkit-scrollbar-thumb { background: var(--ps-scroll-thumb); border-radius: 4px; }
303
327
  ::-webkit-scrollbar-thumb:hover { background: var(--ps-link); }
304
328
 
305
329
  /* ============================================================
@@ -381,7 +405,10 @@ body > #content {
381
405
  }
382
406
 
383
407
  /* The collapse toggle PNG is a dark-on-transparent sprite; invert it
384
- * so the arrow shows on dark backgrounds. */
385
- #full_list li a.toggle {
386
- filter: invert(0.85) hue-rotate(180deg) brightness(1.6);
408
+ * so the arrow shows on dark backgrounds. Dark mode only — in light
409
+ * mode the stock dark glyphs are already correct. */
410
+ @media (prefers-color-scheme: dark) {
411
+ #full_list li a.toggle {
412
+ filter: invert(0.85) hue-rotate(180deg) brightness(1.6);
413
+ }
387
414
  }