@xmachines/play-router 1.0.0-beta.1

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 (141) hide show
  1. package/.oxfmtrc.json +3 -0
  2. package/.oxlintrc.json +3 -0
  3. package/README.md +436 -0
  4. package/coverage/base.css +224 -0
  5. package/coverage/block-navigation.js +87 -0
  6. package/coverage/build-tree.ts.html +316 -0
  7. package/coverage/connect-router.ts.html +505 -0
  8. package/coverage/coverage-summary.json +15 -0
  9. package/coverage/crawl-machine.ts.html +385 -0
  10. package/coverage/create-browser-history.ts.html +556 -0
  11. package/coverage/create-route-map.ts.html +400 -0
  12. package/coverage/create-router.ts.html +328 -0
  13. package/coverage/extract-route.ts.html +322 -0
  14. package/coverage/extract-routes.ts.html +286 -0
  15. package/coverage/favicon.png +0 -0
  16. package/coverage/index.html +296 -0
  17. package/coverage/index.ts.html +610 -0
  18. package/coverage/prettify.css +1 -0
  19. package/coverage/prettify.js +2 -0
  20. package/coverage/query.ts.html +307 -0
  21. package/coverage/router-bridge-base.ts.html +919 -0
  22. package/coverage/sort-arrow-sprite.png +0 -0
  23. package/coverage/sorter.js +210 -0
  24. package/coverage/types.ts.html +787 -0
  25. package/coverage/validate-routes.ts.html +319 -0
  26. package/dist/build-tree.d.ts +13 -0
  27. package/dist/build-tree.d.ts.map +1 -0
  28. package/dist/build-tree.js +67 -0
  29. package/dist/build-tree.js.map +1 -0
  30. package/dist/connect-router.d.ts +56 -0
  31. package/dist/connect-router.d.ts.map +1 -0
  32. package/dist/connect-router.js +119 -0
  33. package/dist/connect-router.js.map +1 -0
  34. package/dist/crawl-machine.d.ts +74 -0
  35. package/dist/crawl-machine.d.ts.map +1 -0
  36. package/dist/crawl-machine.js +95 -0
  37. package/dist/crawl-machine.js.map +1 -0
  38. package/dist/create-browser-history.d.ts +68 -0
  39. package/dist/create-browser-history.d.ts.map +1 -0
  40. package/dist/create-browser-history.js +94 -0
  41. package/dist/create-browser-history.js.map +1 -0
  42. package/dist/create-route-map.d.ts +46 -0
  43. package/dist/create-route-map.d.ts.map +1 -0
  44. package/dist/create-route-map.js +73 -0
  45. package/dist/create-route-map.js.map +1 -0
  46. package/dist/create-router.d.ts +73 -0
  47. package/dist/create-router.d.ts.map +1 -0
  48. package/dist/create-router.js +63 -0
  49. package/dist/create-router.js.map +1 -0
  50. package/dist/extract-route.d.ts +25 -0
  51. package/dist/extract-route.d.ts.map +1 -0
  52. package/dist/extract-route.js +63 -0
  53. package/dist/extract-route.js.map +1 -0
  54. package/dist/extract-routes.d.ts +41 -0
  55. package/dist/extract-routes.d.ts.map +1 -0
  56. package/dist/extract-routes.js +61 -0
  57. package/dist/extract-routes.js.map +1 -0
  58. package/dist/index.d.ts +56 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +141 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/query.d.ts +52 -0
  63. package/dist/query.d.ts.map +1 -0
  64. package/dist/query.js +69 -0
  65. package/dist/query.js.map +1 -0
  66. package/dist/router-bridge-base.d.ts +150 -0
  67. package/dist/router-bridge-base.d.ts.map +1 -0
  68. package/dist/router-bridge-base.js +240 -0
  69. package/dist/router-bridge-base.js.map +1 -0
  70. package/dist/types.d.ts +228 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +2 -0
  73. package/dist/types.js.map +1 -0
  74. package/dist/validate-routes.d.ts +39 -0
  75. package/dist/validate-routes.d.ts.map +1 -0
  76. package/dist/validate-routes.js +65 -0
  77. package/dist/validate-routes.js.map +1 -0
  78. package/examples/demo/README.md +127 -0
  79. package/examples/demo/index.html +41 -0
  80. package/examples/demo/package.json +27 -0
  81. package/examples/demo/src/main.ts +28 -0
  82. package/examples/demo/src/router.ts +37 -0
  83. package/examples/demo/src/shell.ts +316 -0
  84. package/examples/demo/test/browser/auth-flow.browser.test.ts +60 -0
  85. package/examples/demo/test/browser/startup.browser.test.ts +37 -0
  86. package/examples/demo/test/library-pattern.test.ts +51 -0
  87. package/examples/demo/tsconfig.json +17 -0
  88. package/examples/demo/vite.config.ts +7 -0
  89. package/examples/demo/vitest.browser.config.ts +20 -0
  90. package/examples/demo/vitest.config.ts +9 -0
  91. package/examples/shared/dist/auth-machine.d.ts +20 -0
  92. package/examples/shared/dist/auth-machine.d.ts.map +1 -0
  93. package/examples/shared/dist/auth-machine.js +212 -0
  94. package/examples/shared/dist/auth-machine.js.map +1 -0
  95. package/examples/shared/dist/catalog.d.ts +85 -0
  96. package/examples/shared/dist/catalog.d.ts.map +1 -0
  97. package/examples/shared/dist/catalog.js +86 -0
  98. package/examples/shared/dist/catalog.js.map +1 -0
  99. package/examples/shared/dist/index.d.ts +4 -0
  100. package/examples/shared/dist/index.d.ts.map +1 -0
  101. package/examples/shared/dist/index.js +3 -0
  102. package/examples/shared/dist/index.js.map +1 -0
  103. package/examples/shared/package.json +37 -0
  104. package/examples/shared/src/auth-machine.ts +234 -0
  105. package/examples/shared/src/catalog.ts +95 -0
  106. package/examples/shared/src/index.css +3 -0
  107. package/examples/shared/src/index.ts +3 -0
  108. package/examples/shared/src/styles/layout.css +413 -0
  109. package/examples/shared/src/styles/reset.css +42 -0
  110. package/examples/shared/src/styles/tokens.css +183 -0
  111. package/examples/shared/tsconfig.json +14 -0
  112. package/examples/shared/tsconfig.tsbuildinfo +1 -0
  113. package/package.json +44 -0
  114. package/src/build-tree.ts +77 -0
  115. package/src/connect-router.ts +142 -0
  116. package/src/crawl-machine.ts +100 -0
  117. package/src/create-browser-history.ts +157 -0
  118. package/src/create-route-map.ts +105 -0
  119. package/src/create-router.ts +87 -0
  120. package/src/extract-route.ts +79 -0
  121. package/src/extract-routes.ts +67 -0
  122. package/src/index.ts +175 -0
  123. package/src/query.ts +74 -0
  124. package/src/router-bridge-base.ts +279 -0
  125. package/src/types.ts +234 -0
  126. package/src/validate-routes.ts +76 -0
  127. package/test/connect-route-map.test.ts +320 -0
  128. package/test/crawl-extract.test.js +473 -0
  129. package/test/create-browser-history.test.ts +123 -0
  130. package/test/create-router.test.ts +23 -0
  131. package/test/extract-routes.test.ts +80 -0
  132. package/test/find-route-by-path-patterns.test.ts +69 -0
  133. package/test/integration.test.js +438 -0
  134. package/test/query.test.ts +56 -0
  135. package/test/router-bridge-base-edge.test.ts +165 -0
  136. package/test/router-bridge-base.test.ts +119 -0
  137. package/test/tree-query.test.js +692 -0
  138. package/test/validation.test.js +158 -0
  139. package/tsconfig.json +14 -0
  140. package/tsconfig.tsbuildinfo +1 -0
  141. package/vitest.config.ts +35 -0
package/.oxfmtrc.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": ["@xmachines/shared/oxfmt"]
3
+ }
package/.oxlintrc.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": ["@xmachines/shared/oxlint"]
3
+ }
package/README.md ADDED
@@ -0,0 +1,436 @@
1
+ # @xmachines/play-router
2
+
3
+ **Route tree extraction from XState v5 state machines with routing patterns**
4
+
5
+ BFS graph crawling and bidirectional route lookup enabling Actor Authority over navigation.
6
+
7
+ ## Overview
8
+
9
+ `@xmachines/play-router` extracts route trees from XState state machines by crawling the state graph using breadth-first traversal. It extracts `meta.route` paths from state machines and builds hierarchical route trees with bidirectional state ID ↔ path mapping.
10
+
11
+ It also exports `RouterBridgeBase`, the shared base class used by framework adapters to implement `RouterBridge` with consistent actor↔router synchronization behavior.
12
+
13
+ `RouterBridgeBase` is the policy point; framework adapters are thin ports that implement only framework-specific navigate/subscribe/unsubscribe behavior.
14
+
15
+ Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md), this package implements:
16
+
17
+ - **Actor Authority (INV-01):** Routes derive from machine definitions, not external configuration
18
+
19
+ **Routing:** Supports `meta.route` detection, `play.route` event routing, and pattern matching for dynamic parameters.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install xstate@^5.0.0
25
+ npm install @xmachines/play-router
26
+ ```
27
+
28
+ **Peer dependencies:**
29
+
30
+ - `xstate` ^5.0.0 - State machine runtime
31
+
32
+ ## Quick Start
33
+
34
+ ```typescript
35
+ import { createMachine } from "xstate";
36
+ import { extractMachineRoutes } from "@xmachines/play-router";
37
+
38
+ // Route pattern (recommended)
39
+ const machine = createMachine({
40
+ id: "app",
41
+ initial: "home",
42
+ states: {
43
+ home: {
44
+ id: "home",
45
+ meta: { route: "/", view: { component: "Home" } },
46
+ },
47
+ dashboard: {
48
+ id: "dashboard",
49
+ meta: { route: "/dashboard", view: { component: "Dashboard" } },
50
+ initial: "overview",
51
+ states: {
52
+ overview: {
53
+ id: "overview",
54
+ meta: { route: "/overview", view: { component: "Overview" } },
55
+ },
56
+ settings: {
57
+ id: "settings",
58
+ meta: { route: "/settings/:section?", view: { component: "Settings" } }, // Optional parameter
59
+ },
60
+ },
61
+ },
62
+ },
63
+ });
64
+
65
+ const tree = extractMachineRoutes(machine);
66
+
67
+ // Bidirectional lookup
68
+ console.log(tree.byPath.get("/dashboard/overview")); // RouteNode
69
+ console.log(tree.byId.get("overview")); // RouteNode
70
+
71
+ // Pattern matching for dynamic routes
72
+ const settingsRoute = tree.byPath.get("/settings/profile");
73
+ console.log(settingsRoute?.id); // "settings"
74
+ ```
75
+
76
+ ## Vanilla Browser Example
77
+
78
+ See `examples/vanilla-demo/` for a complete example using vanilla TypeScript with the browser History API.
79
+
80
+ The demo demonstrates:
81
+
82
+ - **RouteTree extraction** from XState machine meta.route
83
+ - **History API integration** (pushState, popstate)
84
+ - **Bidirectional synchronization** (actor ↔ URL)
85
+ - **Protected route guards** (authentication redirects)
86
+ - **Dynamic route parameters** (/profile/:userId)
87
+
88
+ **Run the demo:**
89
+
90
+ ```bash
91
+ cd packages/play-router/examples/demo
92
+ npm install
93
+ npm run dev
94
+ ```
95
+
96
+ Open http://localhost:5174/ and explore:
97
+
98
+ 1. Login with any username
99
+ 2. Navigate between home and profile
100
+ 3. Use browser back/forward buttons
101
+ 4. Try accessing protected routes directly
102
+
103
+ **Key implementation patterns:**
104
+
105
+ ```typescript
106
+ // Extract routes from machine
107
+ const routeTree = extractMachineRoutes(authMachine);
108
+ const routeMap = createRouteMap(routeTree);
109
+
110
+ // Actor → URL sync
111
+ const watcher = new Signal.subtle.Watcher(() => {
112
+ queueMicrotask(() => {
113
+ watcher.getPending();
114
+ const route = actor.currentRoute.get();
115
+ if (route) {
116
+ window.history.pushState({}, "", route);
117
+ }
118
+ watcher.watch(actor.currentRoute);
119
+ });
120
+ });
121
+
122
+ // URL → Actor sync (with pattern matching)
123
+ window.addEventListener("popstate", () => {
124
+ const path = window.location.pathname;
125
+ const { to, params } = routeMap.resolve(path);
126
+ if (to) {
127
+ actor.send({ type: "play.route", to, params });
128
+ }
129
+ });
130
+
131
+ // Initial URL handling
132
+ const initialPath = window.location.pathname;
133
+ if (initialPath !== "/") {
134
+ const { to, params } = routeMap.resolve(initialPath);
135
+ if (to) {
136
+ actor.send({ type: "play.route", to, params });
137
+ }
138
+ }
139
+ ```
140
+
141
+ This example shows the core routing concepts without framework dependencies, making it ideal for understanding how @xmachines/play-router integrates with browser History API.
142
+
143
+ ## Canonical Watcher Lifecycle
144
+
145
+ Bridge implementations should use one watcher flow:
146
+
147
+ 1. `notify`
148
+ 2. `queueMicrotask`
149
+ 3. `getPending()`
150
+ 4. read actor route and sync infrastructure state
151
+ 5. re-arm with `watch(...)` or `watch()`
152
+
153
+ Watcher notification is one-shot; re-arm is required.
154
+
155
+ ## Bridge Cleanup Contract
156
+
157
+ Bridge teardown must be explicit and deterministic:
158
+
159
+ - `disconnect`/`dispose` must unwatch signal subscriptions and unhook router listeners.
160
+ - Do not rely on GC-only cleanup guidance.
161
+ - Infrastructure remains passive: bridges observe and forward intents, actors decide validity.
162
+
163
+ ## API Reference
164
+
165
+ ### extractMachineRoutes()
166
+
167
+ Main entry point — crawls state machine, extracts routes, builds tree:
168
+
169
+ ```typescript
170
+ const tree = extractMachineRoutes(machine: AnyStateMachine): RouteTree;
171
+ ```
172
+
173
+ **Detection:**
174
+
175
+ - States with `meta.route` in meta object
176
+
177
+ **Returns:** `RouteTree` with:
178
+
179
+ - `routes: RouteNode[]` - Array of route nodes
180
+ - `byPath: Map<string, RouteNode>` - URL path → route node
181
+ - `byId: Map<string, RouteNode>` - State ID → route node
182
+
183
+ **Throws:** Error if routes are invalid (malformed paths, missing state IDs, duplicates)
184
+
185
+ **Example:**
186
+
187
+ ```typescript
188
+ import { extractMachineRoutes } from "@xmachines/play-router";
189
+
190
+ const tree = extractMachineRoutes(authMachine);
191
+
192
+ // Query routes
193
+ const loginRoute = tree.byId.get("login");
194
+ console.log(loginRoute?.path); // "/login"
195
+
196
+ const dashboardRoute = tree.byPath.get("/dashboard");
197
+ console.log(dashboardRoute?.id); // "dashboard"
198
+ ```
199
+
200
+ ### crawlMachine()
201
+
202
+ Low-level BFS traversal of state machine graph:
203
+
204
+ ```typescript
205
+ const visits = crawlMachine(machine: AnyStateMachine): StateVisit[];
206
+ ```
207
+
208
+ **Returns:** Array of state visits in breadth-first order with:
209
+
210
+ - `path: string[]` - State path (e.g., ["dashboard", "settings"])
211
+ - `parent: StateNode | null` - Parent state node
212
+ - `node: StateNode` - Current state node
213
+
214
+ **Example:**
215
+
216
+ ```typescript
217
+ import { crawlMachine } from "@xmachines/play-router";
218
+
219
+ const visits = crawlMachine(machine);
220
+ visits.forEach((visit) => {
221
+ console.log("State:", visit.path.join("."));
222
+ console.log("Parent:", visit.parent?.id ?? "root");
223
+ });
224
+ ```
225
+
226
+ ### Query Utilities
227
+
228
+ ```typescript
229
+ // Get child routes from state
230
+ const children = getNavigableRoutes(tree, "dashboard");
231
+
232
+ // Check if route exists
233
+ const exists = routeExists(tree, "/profile/:userId");
234
+ ```
235
+
236
+ **Complete API:** See [API Documentation](../../docs/api/@xmachines/play-router)
237
+
238
+ ## Examples
239
+
240
+ ### Route Detection
241
+
242
+ ```typescript
243
+ import { extractMachineRoutes } from "@xmachines/play-router";
244
+ import { createMachine } from "xstate";
245
+
246
+ const machine = createMachine({
247
+ initial: "home",
248
+ states: {
249
+ home: {
250
+ id: "home",
251
+ meta: { route: "/", view: { component: "Home" } },
252
+ },
253
+ profile: {
254
+ id: "profile",
255
+ meta: { route: "/profile/:userId", view: { component: "Profile" } }, // Parameter pattern
256
+ },
257
+ settings: {
258
+ id: "settings",
259
+ meta: { route: "/settings/:section?", view: { component: "Settings" } }, // Optional parameter
260
+ },
261
+ },
262
+ });
263
+
264
+ const tree = extractMachineRoutes(machine);
265
+
266
+ // Bidirectional mapping
267
+ const profileById = tree.byId.get("profile");
268
+ console.log(profileById?.path); // "/profile/:userId"
269
+
270
+ const profileByPath = tree.byPath.get("/profile/user123");
271
+ console.log(profileByPath?.id); // "profile"
272
+ ```
273
+
274
+ ### Hierarchical Route Tree
275
+
276
+ ```typescript
277
+ import { extractMachineRoutes, getNavigableRoutes } from "@xmachines/play-router";
278
+
279
+ const machine = createMachine({
280
+ initial: "app",
281
+ states: {
282
+ app: {
283
+ id: "app",
284
+ meta: { route: "/", view: { component: "AppShell" } },
285
+ initial: "dashboard",
286
+ states: {
287
+ dashboard: {
288
+ id: "dashboard",
289
+ meta: { route: "/dashboard", view: { component: "Dashboard" } },
290
+ initial: "overview",
291
+ states: {
292
+ overview: {
293
+ id: "overview",
294
+ meta: { route: "/overview", view: { component: "Overview" } },
295
+ },
296
+ analytics: {
297
+ id: "analytics",
298
+ meta: { route: "/analytics", view: { component: "Analytics" } },
299
+ },
300
+ },
301
+ },
302
+ },
303
+ },
304
+ },
305
+ });
306
+
307
+ const tree = extractMachineRoutes(machine);
308
+
309
+ // Get child routes
310
+ const dashboardChildren = getNavigableRoutes(tree, "dashboard");
311
+ console.log(dashboardChildren.map((r) => r.id)); // ["overview", "analytics"]
312
+
313
+ // Route inheritance
314
+ const analyticsRoute = tree.byId.get("analytics");
315
+ console.log(analyticsRoute?.path); // "/dashboard/analytics" (inherited parent path)
316
+ ```
317
+
318
+ ### Pattern Matching
319
+
320
+ ```typescript
321
+ import { extractMachineRoutes } from "@xmachines/play-router";
322
+
323
+ const machine = createMachine({
324
+ states: {
325
+ user: {
326
+ id: "user",
327
+ meta: { route: "/user/:userId", view: { component: "User" } }, // Required parameter
328
+ },
329
+ settings: {
330
+ id: "settings",
331
+ meta: { route: "/settings/:section?", view: { component: "Settings" } }, // Optional parameter
332
+ },
333
+ },
334
+ });
335
+
336
+ const tree = extractMachineRoutes(machine);
337
+
338
+ // Pattern matching for actual URLs
339
+ const userRoute = tree.byPath.get("/user/user123");
340
+ console.log(userRoute?.id); // "user"
341
+
342
+ const settingsDefault = tree.byPath.get("/settings");
343
+ console.log(settingsDefault?.id); // "settings" (optional param)
344
+
345
+ const settingsProfile = tree.byPath.get("/settings/profile");
346
+ console.log(settingsProfile?.id); // "settings" (with param)
347
+ ```
348
+
349
+ ## Route Configuration
350
+
351
+ ### Route Pattern (Recommended)
352
+
353
+ ```typescript
354
+ states: {
355
+ dashboard: {
356
+ id: "dashboard", // Required for bidirectional lookup
357
+ meta: {
358
+ route: "/dashboard", // URL path - marks state as routable
359
+ },
360
+ },
361
+ }
362
+ ```
363
+
364
+ ### Alternative Pattern
365
+
366
+ ```typescript
367
+ states: {
368
+ dashboard: {
369
+ id: "dashboard",
370
+ meta: {
371
+ route: "/dashboard",
372
+ },
373
+ },
374
+ }
375
+ ```
376
+
377
+ ### Route Inheritance
378
+
379
+ ```typescript
380
+ states: {
381
+ parent: {
382
+ id: "parent",
383
+ meta: { route: "/parent", view: { component: "Parent" } },
384
+ states: {
385
+ absolute: {
386
+ id: "absolute",
387
+ meta: { route: "/absolute", view: { component: "Absolute" } }, // Starts with / → doesn't inherit
388
+ },
389
+ relative: {
390
+ id: "relative",
391
+ meta: { route: "relative", view: { component: "Relative" } }, // No leading / → inherits parent
392
+ // Final path: "/parent/relative"
393
+ },
394
+ },
395
+ },
396
+ }
397
+ ```
398
+
399
+ ### Dynamic Parameters
400
+
401
+ ```typescript
402
+ meta: {
403
+ route: "/profile/:userId", // Required parameter
404
+ route: "/settings/:section?", // Optional parameter
405
+ route: "/docs/:category/:page", // Multiple parameters
406
+ view: { component: "AnyView" },
407
+ }
408
+ ```
409
+
410
+ **Parameter substitution:** Values extracted from context or event params (handled by play-xstate adapter).
411
+
412
+ ## Architecture
413
+
414
+ This package enables **Actor Authority (INV-01)**:
415
+
416
+ 1. **Routes derive from machine:** Business logic defines routes in state machine, not external config
417
+ 2. **BFS traversal:** Systematic state discovery ensures all nested states visited
418
+ 3. **Bidirectional mapping:** Fast lookup by path (browser URL) or by ID (state machine)
419
+ 4. **Build-time validation:** Invalid routes throw errors during extraction, not runtime
420
+
421
+ **Enhancements:**
422
+
423
+ - `meta.route` detection via state metadata
424
+ - Pattern matching for dynamic routes (`:param` and `:param?`)
425
+ - State ID ↔ path bidirectional maps for `play.route` events
426
+
427
+ ## Related Packages
428
+
429
+ - **[@xmachines/play-xstate](../play-xstate)** - XState adapter using route extraction
430
+ - **[@xmachines/play-tanstack-react-router](../play-tanstack-react-router)** - TanStack Router adapter using route trees
431
+ - **[@xmachines/play-react-router](../play-react-router)** - React Router v7 adapter using RouterBridgeBase
432
+ - **[@xmachines/play](../play)** - Protocol types
433
+
434
+ ## License
435
+
436
+ MIT
@@ -0,0 +1,224 @@
1
+ body, html {
2
+ margin:0; padding: 0;
3
+ height: 100%;
4
+ }
5
+ body {
6
+ font-family: Helvetica Neue, Helvetica, Arial;
7
+ font-size: 14px;
8
+ color:#333;
9
+ }
10
+ .small { font-size: 12px; }
11
+ *, *:after, *:before {
12
+ -webkit-box-sizing:border-box;
13
+ -moz-box-sizing:border-box;
14
+ box-sizing:border-box;
15
+ }
16
+ h1 { font-size: 20px; margin: 0;}
17
+ h2 { font-size: 14px; }
18
+ pre {
19
+ font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
20
+ margin: 0;
21
+ padding: 0;
22
+ -moz-tab-size: 2;
23
+ -o-tab-size: 2;
24
+ tab-size: 2;
25
+ }
26
+ a { color:#0074D9; text-decoration:none; }
27
+ a:hover { text-decoration:underline; }
28
+ .strong { font-weight: bold; }
29
+ .space-top1 { padding: 10px 0 0 0; }
30
+ .pad2y { padding: 20px 0; }
31
+ .pad1y { padding: 10px 0; }
32
+ .pad2x { padding: 0 20px; }
33
+ .pad2 { padding: 20px; }
34
+ .pad1 { padding: 10px; }
35
+ .space-left2 { padding-left:55px; }
36
+ .space-right2 { padding-right:20px; }
37
+ .center { text-align:center; }
38
+ .clearfix { display:block; }
39
+ .clearfix:after {
40
+ content:'';
41
+ display:block;
42
+ height:0;
43
+ clear:both;
44
+ visibility:hidden;
45
+ }
46
+ .fl { float: left; }
47
+ @media only screen and (max-width:640px) {
48
+ .col3 { width:100%; max-width:100%; }
49
+ .hide-mobile { display:none!important; }
50
+ }
51
+
52
+ .quiet {
53
+ color: #7f7f7f;
54
+ color: rgba(0,0,0,0.5);
55
+ }
56
+ .quiet a { opacity: 0.7; }
57
+
58
+ .fraction {
59
+ font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
60
+ font-size: 10px;
61
+ color: #555;
62
+ background: #E8E8E8;
63
+ padding: 4px 5px;
64
+ border-radius: 3px;
65
+ vertical-align: middle;
66
+ }
67
+
68
+ div.path a:link, div.path a:visited { color: #333; }
69
+ table.coverage {
70
+ border-collapse: collapse;
71
+ margin: 10px 0 0 0;
72
+ padding: 0;
73
+ }
74
+
75
+ table.coverage td {
76
+ margin: 0;
77
+ padding: 0;
78
+ vertical-align: top;
79
+ }
80
+ table.coverage td.line-count {
81
+ text-align: right;
82
+ padding: 0 5px 0 20px;
83
+ }
84
+ table.coverage td.line-coverage {
85
+ text-align: right;
86
+ padding-right: 10px;
87
+ min-width:20px;
88
+ }
89
+
90
+ table.coverage td span.cline-any {
91
+ display: inline-block;
92
+ padding: 0 5px;
93
+ width: 100%;
94
+ }
95
+ .missing-if-branch {
96
+ display: inline-block;
97
+ margin-right: 5px;
98
+ border-radius: 3px;
99
+ position: relative;
100
+ padding: 0 4px;
101
+ background: #333;
102
+ color: yellow;
103
+ }
104
+
105
+ .skip-if-branch {
106
+ display: none;
107
+ margin-right: 10px;
108
+ position: relative;
109
+ padding: 0 4px;
110
+ background: #ccc;
111
+ color: white;
112
+ }
113
+ .missing-if-branch .typ, .skip-if-branch .typ {
114
+ color: inherit !important;
115
+ }
116
+ .coverage-summary {
117
+ border-collapse: collapse;
118
+ width: 100%;
119
+ }
120
+ .coverage-summary tr { border-bottom: 1px solid #bbb; }
121
+ .keyline-all { border: 1px solid #ddd; }
122
+ .coverage-summary td, .coverage-summary th { padding: 10px; }
123
+ .coverage-summary tbody { border: 1px solid #bbb; }
124
+ .coverage-summary td { border-right: 1px solid #bbb; }
125
+ .coverage-summary td:last-child { border-right: none; }
126
+ .coverage-summary th {
127
+ text-align: left;
128
+ font-weight: normal;
129
+ white-space: nowrap;
130
+ }
131
+ .coverage-summary th.file { border-right: none !important; }
132
+ .coverage-summary th.pct { }
133
+ .coverage-summary th.pic,
134
+ .coverage-summary th.abs,
135
+ .coverage-summary td.pct,
136
+ .coverage-summary td.abs { text-align: right; }
137
+ .coverage-summary td.file { white-space: nowrap; }
138
+ .coverage-summary td.pic { min-width: 120px !important; }
139
+ .coverage-summary tfoot td { }
140
+
141
+ .coverage-summary .sorter {
142
+ height: 10px;
143
+ width: 7px;
144
+ display: inline-block;
145
+ margin-left: 0.5em;
146
+ background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
147
+ }
148
+ .coverage-summary .sorted .sorter {
149
+ background-position: 0 -20px;
150
+ }
151
+ .coverage-summary .sorted-desc .sorter {
152
+ background-position: 0 -10px;
153
+ }
154
+ .status-line { height: 10px; }
155
+ /* yellow */
156
+ .cbranch-no { background: yellow !important; color: #111; }
157
+ /* dark red */
158
+ .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
159
+ .low .chart { border:1px solid #C21F39 }
160
+ .highlighted,
161
+ .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
162
+ background: #C21F39 !important;
163
+ }
164
+ /* medium red */
165
+ .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
166
+ /* light red */
167
+ .low, .cline-no { background:#FCE1E5 }
168
+ /* light green */
169
+ .high, .cline-yes { background:rgb(230,245,208) }
170
+ /* medium green */
171
+ .cstat-yes { background:rgb(161,215,106) }
172
+ /* dark green */
173
+ .status-line.high, .high .cover-fill { background:rgb(77,146,33) }
174
+ .high .chart { border:1px solid rgb(77,146,33) }
175
+ /* dark yellow (gold) */
176
+ .status-line.medium, .medium .cover-fill { background: #f9cd0b; }
177
+ .medium .chart { border:1px solid #f9cd0b; }
178
+ /* light yellow */
179
+ .medium { background: #fff4c2; }
180
+
181
+ .cstat-skip { background: #ddd; color: #111; }
182
+ .fstat-skip { background: #ddd; color: #111 !important; }
183
+ .cbranch-skip { background: #ddd !important; color: #111; }
184
+
185
+ span.cline-neutral { background: #eaeaea; }
186
+
187
+ .coverage-summary td.empty {
188
+ opacity: .5;
189
+ padding-top: 4px;
190
+ padding-bottom: 4px;
191
+ line-height: 1;
192
+ color: #888;
193
+ }
194
+
195
+ .cover-fill, .cover-empty {
196
+ display:inline-block;
197
+ height: 12px;
198
+ }
199
+ .chart {
200
+ line-height: 0;
201
+ }
202
+ .cover-empty {
203
+ background: white;
204
+ }
205
+ .cover-full {
206
+ border-right: none !important;
207
+ }
208
+ pre.prettyprint {
209
+ border: none !important;
210
+ padding: 0 !important;
211
+ margin: 0 !important;
212
+ }
213
+ .com { color: #999 !important; }
214
+ .ignore-none { color: #999; font-weight: normal; }
215
+
216
+ .wrapper {
217
+ min-height: 100%;
218
+ height: auto !important;
219
+ height: 100%;
220
+ margin: 0 auto -48px;
221
+ }
222
+ .footer, .push {
223
+ height: 48px;
224
+ }