rori 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/LICENSE +21 -0
  4. data/README.md +279 -0
  5. data/app/assets/stylesheets/rori/_init.css +79 -0
  6. data/app/assets/stylesheets/rori/layout.css +162 -0
  7. data/app/assets/stylesheets/rori/menubar.css +117 -0
  8. data/app/assets/stylesheets/rori/notifications.css +120 -0
  9. data/app/assets/stylesheets/rori/palette.css +114 -0
  10. data/app/assets/stylesheets/rori/shortcuts.css +110 -0
  11. data/app/assets/stylesheets/rori/terminal.css +98 -0
  12. data/app/assets/stylesheets/rori/themes.css +156 -0
  13. data/app/assets/stylesheets/rori/typing_badge.css +56 -0
  14. data/app/assets/stylesheets/rori/wallpaper.css +52 -0
  15. data/app/assets/stylesheets/rori/window.css +174 -0
  16. data/app/controllers/concerns/rori/windowed.rb +15 -0
  17. data/app/controllers/rori/commands/bars_controller.rb +9 -0
  18. data/app/controllers/rori/commands/runs_controller.rb +22 -0
  19. data/app/controllers/rori/commands/themes_controller.rb +9 -0
  20. data/app/controllers/rori/commands/ui_controller.rb +9 -0
  21. data/app/controllers/rori/commands/wallpapers_controller.rb +9 -0
  22. data/app/controllers/rori/commands_controller.rb +7 -0
  23. data/app/controllers/rori/desktops_controller.rb +7 -0
  24. data/app/helpers/rori/assets_helper.rb +9 -0
  25. data/app/helpers/rori/keymap_helper.rb +50 -0
  26. data/app/helpers/rori/prefs_helper.rb +13 -0
  27. data/app/helpers/rori/theme_helper.rb +14 -0
  28. data/app/helpers/rori/wallpaper_helper.rb +19 -0
  29. data/app/helpers/rori/window_helper.rb +36 -0
  30. data/app/javascript/rori/clock_controller.js +18 -0
  31. data/app/javascript/rori/command_index.js +84 -0
  32. data/app/javascript/rori/fuzzy.js +50 -0
  33. data/app/javascript/rori/index.js +21 -0
  34. data/app/javascript/rori/notifications_controller.js +45 -0
  35. data/app/javascript/rori/palette_controller.js +307 -0
  36. data/app/javascript/rori/prefs_controller.js +68 -0
  37. data/app/javascript/rori/rori_controller.js +996 -0
  38. data/app/javascript/rori/terminal_controller.js +198 -0
  39. data/app/javascript/rori/theme_controller.js +35 -0
  40. data/app/javascript/rori/wallpaper_controller.js +67 -0
  41. data/app/models/rori/command.rb +105 -0
  42. data/app/models/rori/notification.rb +20 -0
  43. data/app/models/rori/theme/color.rb +30 -0
  44. data/app/models/rori/theme.rb +87 -0
  45. data/app/models/rori/wallpaper.rb +54 -0
  46. data/app/views/layouts/rori/_head.html.haml +2 -0
  47. data/app/views/layouts/rori/shell.html.haml +97 -0
  48. data/app/views/layouts/rori/window.html.haml +10 -0
  49. data/app/views/rori/_menubar.html.haml +14 -0
  50. data/app/views/rori/_notification.html.haml +9 -0
  51. data/app/views/rori/_notifications.html.haml +10 -0
  52. data/app/views/rori/_palette.html.haml +13 -0
  53. data/app/views/rori/_shortcuts.html.haml +47 -0
  54. data/app/views/rori/_terminal.html.haml +9 -0
  55. data/app/views/rori/_wallpaper.html.haml +7 -0
  56. data/app/views/rori/_wallpaper_credit.html.haml +3 -0
  57. data/app/views/rori/_window.html.haml +15 -0
  58. data/app/views/rori/commands/_command.html.haml +17 -0
  59. data/app/views/rori/commands/index.html.haml +5 -0
  60. data/app/views/rori/desktops/show.html.haml +1 -0
  61. data/config/importmap.rb +3 -0
  62. data/config/locales/rori.en.yml +133 -0
  63. data/config/routes/rori.rb +13 -0
  64. data/lib/generators/rori/install/install_generator.rb +67 -0
  65. data/lib/generators/rori/install/templates/rori.rb.tt +22 -0
  66. data/lib/rori/engine.rb +19 -0
  67. data/lib/rori/version.rb +3 -0
  68. data/lib/rori.rb +180 -0
  69. data/lib/tasks/rori.rake +9 -0
  70. data/vendor/themes/LICENSE +25 -0
  71. data/vendor/themes/ghostty/Catppuccin Latte +22 -0
  72. data/vendor/themes/ghostty/Catppuccin Mocha +22 -0
  73. data/vendor/themes/ghostty/Everforest Dark Med +22 -0
  74. data/vendor/themes/ghostty/Flexoki Light +22 -0
  75. data/vendor/themes/ghostty/GitHub Dark Dimmed +22 -0
  76. data/vendor/themes/ghostty/Gruvbox Material Dark +22 -0
  77. data/vendor/themes/ghostty/Kanagawa Wave +22 -0
  78. data/vendor/themes/ghostty/Melange Dark +22 -0
  79. data/vendor/themes/ghostty/Nord +22 -0
  80. data/vendor/themes/ghostty/Rose Pine Dawn +22 -0
  81. data/vendor/themes/ghostty/Rose Pine Moon +22 -0
  82. data/vendor/themes/ghostty/TokyoNight Storm +22 -0
  83. data/vendor/themes/ghostty/Tomorrow Night Eighties +22 -0
  84. data/vendor/themes/ghostty/Zenbones Light +22 -0
  85. data/vendor/wallpapers/unsplash.yml +23 -0
  86. metadata +219 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 32d92384953e7fc7af8e9f2fc5e70aed475154cbb70b0e283419ec5e8516af47
4
+ data.tar.gz: a1a13b88264f163a1678ac6f576bb6564c53c888cb47a2536ac4efde460d4d71
5
+ SHA512:
6
+ metadata.gz: 1dcbd2c85a36c19fce60a63a840ca7906f59884b525a3f70c862c409d57a3e369811c6e442f0fa3dd7577f44007e2d8d9dc859e8566fe5fbbc88aa0171fc6624
7
+ data.tar.gz: 8c4c1ba90c1ddd0a6aaeeb24de45a91fb793e82a480319a61c8ee8ebccf830b72d85461c4afe69c1abf28bbafb176628b1cfd29bad1c73bf81fe624c6d4d4582
data/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ Notable changes to rori, newest first. Each entry names who it's for:
4
+ **Users** (people working in the desk) and **Developers** (apps building on
5
+ it). History before the gem got its own repository lives in
6
+ [rori-demo's changelog](https://github.com/varyform/rori-demo/blob/main/CHANGELOG.md).
7
+
8
+ ## 0.1.0 (2026-09-26)
9
+
10
+ The first release.
11
+
12
+ ### The desk
13
+ - **Users:** every page opens as a window in endlessly scrolling column strips, one strip per workspace, stacked vertically: keyboard focus and moves, column widths, stacking, full width, centring, an overview and a column minimap. The layout survives a reload.
14
+ - **Users:** ⌘K palette and a drop-down terminal (`` ` ``) over one command tree — pages, recent records, desk actions, UI settings — with fuzzy matching across nested lists (`uthen` → UI › Theme › Nord), frecency, shortcuts shown next to commands, Tab completion and history in the terminal.
15
+ - **Users:** Blender-style hover keys (opt-in): bare keys act on the window under the pointer while the cursor stays in your field — `W` close, `U` reopen, `R` width, `F` full, `C` centre, `[` `]` stack, `1–9` switch workspace, `⇧1–9` move the column there and follow it.
16
+ - **Users:** a keyboard shortcuts modal (`⌥?`) generated from the live keymap; closed windows reopen where they were (`⌥⇧T`); unsaved forms ask before closing.
17
+ - **Users:** 14 bundled Ghostty colour themes with live preview; optional Unsplash wallpapers (safe / cover menu bar / off, next, pin); the menu bar at the top or bottom.
18
+ - **Users:** notifications in the desk's corner for server-side commands and background work.
19
+
20
+ ### For host apps
21
+ - **Developers:** `bin/rails g rori:install` wires an app up: initializer, `Rori::Windowed`, `draw :rori`, the root route and the Stimulus registration.
22
+ - **Developers:** pages declare how they're shown (`window size:, mode:, workspace:, key:`); windows with the same key are reused; broadcast refreshes reload only the windows showing the changed data.
23
+ - **Developers:** `Rori.records` lists recent records in ⌘K; a labelled parameterless GET route becomes a command; `Rori.commands` adds entries; `Rori.command :name, confirm: true do … end` runs code on the server, and `Rori.notify` reports back from anywhere (e.g. a job).
24
+ - **Developers:** one keymap (`Rori.keymap`, `Mod` = `Rori.modifier`), a native-shell modifier by user agent (`Rori.native_user_agent`, `Rori.native_modifier`, `Rori.keymap_overrides`).
25
+ - **Developers:** self-contained CSS in one `rori` cascade layer with `--rori-*` tokens that fall back to the host's tokens; `rori-` prefixed classes; `Rori.themes_folder` and `Rori.wallpapers_folder` for the app's own themes and photos.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oleh Khomei
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,279 @@
1
+ # Rori
2
+
3
+ [![CI](https://github.com/varyform/rori/actions/workflows/ci.yml/badge.svg)](https://github.com/varyform/rori/actions/workflows/ci.yml)
4
+
5
+ A niri-style window manager for Rails pages — a "desk" of windows. Every
6
+ page opens as a window in endlessly scrolling column strips, one strip per workspace; ⌘K and a
7
+ drop-down terminal (`` ` ``) run one fuzzy-matched command tree; a keymap
8
+ with a configurable modifier (⌘ in a native shell) and optional hover keys
9
+ drive the windows. Ghostty themes and Unsplash wallpapers included.
10
+
11
+ ![From an empty desk: ⌘K opens pages as windows, focus moves along the strip, the overview zooms out, and the terminal switches the theme](docs/demo.webp)
12
+
13
+ A Rails engine (not isolated): it wraps the host's own controllers and views.
14
+
15
+ See it wired into a small app in [rori-demo](https://github.com/varyform/rori-demo):
16
+ configuration, window options on pages, a server-side command with a
17
+ background job, and a Tauri macOS wrapper.
18
+
19
+ ## A tour
20
+
21
+ **Windows in strips**: columns of any width, windows stacked inside them, the
22
+ strip running off the edges; one strip per workspace.
23
+
24
+ ![A workspace with a users list, two users stacked in one column, and a service running off the right edge](docs/screenshots/desk.webp)
25
+
26
+ **⌘K** matches whole paths through nested lists: `uthen` finds UI › Theme › Nord.
27
+
28
+ ![The command palette with "uthen" matching theme entries](docs/screenshots/palette.webp)
29
+
30
+ **The terminal** (`` ` ``) runs the same commands as words, with Tab completion
31
+ and history. Server-side commands can ask first and report back as
32
+ notifications.
33
+
34
+ ![The drop-down terminal after a confirmed Reindex search, with its notification](docs/screenshots/terminal.webp)
35
+
36
+ **Overview** (⌥O) zooms out to every workspace.
37
+
38
+ ![Overview of three workspaces](docs/screenshots/overview.webp)
39
+
40
+ **Hover keys** (opt-in): point at an inactive window and bare keys act on it
41
+ (`W` closes, `R` cycles its width, `⇧1–9` sends it to a workspace) while the
42
+ cursor stays in the field you're typing in.
43
+
44
+ ![A window marked "keys → here" beside a form whose field has focus](docs/screenshots/hover-keys.webp)
45
+
46
+ **Themes**: bundled Ghostty palettes, or your own theme files.
47
+
48
+ ![The same desk in Rosé Pine Dawn](docs/screenshots/theme-light.webp)
49
+
50
+ **Every shortcut** in one place (⌥?), generated from the live keymap.
51
+
52
+ ![The keyboard shortcuts modal](docs/screenshots/shortcuts.webp)
53
+
54
+ ## Requirements
55
+
56
+ Rails 8 with Propshaft, importmap, Turbo and Stimulus.
57
+
58
+ Your app's views stay whatever they are — ERB, Phlex, anything. Only the
59
+ engine's own views are written in HAML, so the `haml` gem comes along as a
60
+ dependency; you never have to write a line of it.
61
+
62
+ > **Why HAML?** The desk's views are nothing but nesting, and HAML makes
63
+ > nesting the syntax: no closing tags, no towers of `<% end %>`. The ERB
64
+ > version had more angle brackets than a Rust signature with lifetimes —
65
+ > `fn render<'a, T: View + 'a>(v: &'a T) -> Result<Box<dyn Html + 'a>, Error>`
66
+ > — and nobody should have to read that at 2 a.m.
67
+
68
+ ## Wiring it into an app
69
+
70
+ ```ruby
71
+ # Gemfile
72
+ gem "rori"
73
+ ```
74
+
75
+ ```sh
76
+ bundle install
77
+ bin/rails g rori:install
78
+ ```
79
+
80
+ The generator adds `config/initializers/rori.rb`, includes `Rori::Windowed`
81
+ in `ApplicationController`, draws the desk's routes (and makes the blank desk
82
+ the root, unless you have one), and registers the Stimulus controllers. Run
83
+ it again any time; steps already in place are skipped. By hand, that's:
84
+
85
+ ```ruby
86
+ # app/controllers/application_controller.rb — every page renders as a window
87
+ class ApplicationController < ActionController::Base
88
+ include Rori::Windowed
89
+ end
90
+
91
+ # config/routes.rb
92
+ Rails.application.routes.draw do
93
+ draw :rori # ⌘K / terminal command lists
94
+ root "rori/desktops#show" # the blank desk
95
+ end
96
+
97
+ # config/initializers/rori.rb
98
+ Rori.configure do |rori|
99
+ rori.records = %w[ User Project ] # recent records listed in ⌘K
100
+ end
101
+ ```
102
+
103
+ ```js
104
+ // app/javascript/controllers/index.js
105
+ import { registerRori } from "rori"
106
+ registerRori(application)
107
+ ```
108
+
109
+ Pages declare how they're shown with `window size:, mode:, workspace:, key:`
110
+ (see `Rori::WindowHelper`); links that should open a window use
111
+ `rori_link_to`. A parameterless GET route shows up in ⌘K once it has a
112
+ label under `rori.commands.routes.<controller>.<action>` in the app's locale.
113
+
114
+ ## CSS
115
+
116
+ The desk's CSS lives in its own cascade layer, `rori` (sub-layers
117
+ `rori.reset`, `rori.tokens`, `rori.base`, `rori.layout`, `rori.components`,
118
+ `rori.themes`), so it never mixes into the host's layers. Place it among
119
+ yours with one name:
120
+
121
+ ```css
122
+ @layer reset, base, rori, layout, components, utilities;
123
+ ```
124
+
125
+ Unmentioned, it lands after your layers (it loads later).
126
+
127
+ It needs nothing from the host: its components use only `--rori-*` tokens,
128
+ each falling back from a host token when present — `--color-canvas`,
129
+ `--color-surface`, `--color-ink`, `--color-ink-muted`, `--color-line`,
130
+ `--color-primary`, `--color-on-primary`, `--color-success`, `--color-danger`,
131
+ `--color-hover`, `--color-backdrop`, `--gap`, `--radius`, `--radius-sm`,
132
+ `--ease`, `--motion`, `--font-sans`, `--font-mono` — to a built-in default.
133
+ Themes set those host tokens, so they re-colour the host's pages as well.
134
+
135
+ ## What the host provides
136
+
137
+ - **Page styles** (buttons, forms, tables); the desk styles only its chrome.
138
+ - **Head tags** (favicons…): override `app/views/layouts/rori/_head.html.haml`.
139
+
140
+ ## Configuration
141
+
142
+ See `lib/rori.rb` for everything: `app_name`, `records`, `commands`, `keymap`,
143
+ `modifier` / `native_modifier` / `native_user_agent`, `hover_keys`,
144
+ `terminal_key`, `wallpapers`, theme and wallpaper files and cookies.
145
+
146
+ ### Records in ⌘K
147
+
148
+ ```ruby
149
+ Rori.configure do |rori|
150
+ rori.records = %w[ User Project ]
151
+ rori.record_limit = 25 # the default
152
+ end
153
+ ```
154
+
155
+ Each time ⌘K opens (and when the terminal loads its commands), the desk lists
156
+ the `record_limit` most recently updated records of every model named here,
157
+ so typing “ada” jumps straight to that user. An entry is labelled with
158
+ `record.to_s`, grouped by `Model.model_name.human`, and opens
159
+ `polymorphic_path(record)` in a window. Each model therefore needs:
160
+
161
+ - an `updated_at` column (the list is ordered by it);
162
+ - a show route, e.g. `resources :users` (`polymorphic_path` raises without one);
163
+ - a meaningful `to_s`, or the label reads `#<User:0x…>`.
164
+
165
+ It's a shortcut to recent work, not a search: older records don't appear, and
166
+ each model costs one query per open. Leaving a model out only drops its
167
+ records from ⌘K; its pages still open from links, and its index can still be
168
+ listed through a route label (`rori.commands.routes.<controller>.index`).
169
+
170
+ ### Server-side commands and notifications
171
+
172
+ ```ruby
173
+ Rori.configure do |rori|
174
+ rori.command :reindex_search, confirm: true do
175
+ SearchReindexJob.perform_later
176
+ I18n.t("search.reindex_started") # optional: the notification's text (default "Done")
177
+ end
178
+ end
179
+ ```
180
+
181
+ ```yaml
182
+ en:
183
+ rori:
184
+ commands:
185
+ custom:
186
+ reindex_search: Reindex search
187
+ ```
188
+
189
+ The command shows up in ⌘K and the terminal under **Run**. Picking it POSTs
190
+ to `/rori/commands/runs`, runs the block and shows the result as a corner
191
+ notification (errors stay until dismissed). `confirm: true` asks first: ↵
192
+ twice in ⌘K, `y` in the terminal. The block runs inside the request, so
193
+ hand slow work to a job, which can report back from anywhere:
194
+
195
+ ```ruby
196
+ Rori.notify "Search reindexed", "1,204 records", kind: :success # :info, :success, :error
197
+ ```
198
+
199
+ `Rori.notify` broadcasts over Action Cable to `Rori.notifications_stream`,
200
+ which every open desk subscribes to — all users see it, so keep it to
201
+ single-user or admin desks.
202
+
203
+ ### Your own themes and wallpapers
204
+
205
+ ```ruby
206
+ Rori.configure do |rori|
207
+ rori.themes_folder = "config/themes" # Ghostty theme files (relative to Rails.root)
208
+ rori.wallpapers = true
209
+ rori.wallpapers_folder = "wallpapers" # app/assets/images/wallpapers/*.{jpg,png,webp,avif}
210
+ end
211
+ ```
212
+
213
+ - **Themes:** any Ghostty theme file (e.g. from
214
+ [iTerm2-Color-Schemes/ghostty](https://github.com/mbadolato/iTerm2-Color-Schemes/tree/master/ghostty))
215
+ dropped into the folder shows up in UI › Theme next to the bundled ones; a
216
+ file with a bundled theme's name replaces it. No build step — their CSS is
217
+ rendered into the page.
218
+ - **Wallpapers:** the folder is a path inside the app's asset load path, so
219
+ images are fingerprinted and served by Propshaft. When set, its images
220
+ replace the bundled Unsplash photos.
221
+ - UI › Wallpaper › **Next wallpaper** swaps the photo in place; **Pin
222
+ wallpaper** keeps the current one across launches (toggle).
223
+
224
+ The bundled themes are compiled into the gem's `themes.css`; after changing
225
+ `Rori.themes_directory`, run `bin/rails rori:themes:build`.
226
+
227
+ ## Customising the look
228
+
229
+ The desk's chrome reads only its own `--rori-*` tokens, and each one defaults
230
+ to your token of the same meaning. So there are three levels, from broad to
231
+ precise:
232
+
233
+ 1. **Your design tokens** — set them as usual; the desk follows:
234
+ ```css
235
+ :root { --radius: 4px; --radius-sm: 2px; --gap: 8px; --font-sans: "Inter", sans-serif; }
236
+ ```
237
+ Supported: `--color-canvas|surface|ink|ink-muted|line|primary|on-primary|success|danger|hover|backdrop`,
238
+ `--gap`, `--radius`, `--radius-sm`, `--ease`, `--motion`, `--font-sans`, `--font-mono`.
239
+ 2. **Desk-only tokens** — change the desk without touching your pages:
240
+ ```css
241
+ @layer overrides { /* any layer AFTER `rori`, or unlayered */
242
+ :root { --rori-radius: 0; --rori-gap: 16px; }
243
+ }
244
+ ```
245
+ 3. **Components** — every chrome element has a `rori-` class
246
+ (`.rori-win`, `.rori-win__bar`, `.rori-menubar`, `.rori-minimap`,
247
+ `.rori-palette`, `.rori-terminal`, `.rori-col`…):
248
+ ```css
249
+ @layer overrides {
250
+ .rori-win { border-radius: 0; box-shadow: none; }
251
+ .rori-win__bar { height: 26px; }
252
+ }
253
+ ```
254
+
255
+ **The one rule: overrides must come after the `rori` layer.** Cascade layers
256
+ beat specificity, so a rule in a layer *before* `rori` loses even with a more
257
+ specific selector — and so does a `--rori-*` token set there, since
258
+ `rori.tokens` defines them on `:root` too. Put overrides in a later layer
259
+ (`components`, `utilities`, a dedicated `overrides`) or leave them unlayered.
260
+ Overriding your own tokens (level 1) works from any layer, because the desk
261
+ only reads them.
262
+
263
+ Themes set the `--color-*` tokens, so a theme picked in UI › Theme wins over
264
+ level-1 colours; use level 2 (`--rori-ink`, `--rori-surface`…) for colours a
265
+ theme mustn't change.
266
+
267
+ ## Development
268
+
269
+ ```sh
270
+ bundle install
271
+ bundle exec rake test # models, controllers, the generator, against test/dummy
272
+ bundle exec rubocop
273
+ bin/rails server # the dummy app, for poking at the desk
274
+ ```
275
+
276
+ To work on the gem inside a real app, use the GitHub source in the app's
277
+ Gemfile (`gem "rori", github: "varyform/rori", branch: "main"`) and point
278
+ Bundler at your checkout: `bundle config set --local local.rori /path/to/rori`.
279
+ Releases: see `RELEASING.md`.
@@ -0,0 +1,79 @@
1
+ /* The desk's CSS lives in one `desk` layer with its own sub-layers, so it never
2
+ mixes into the host's layers. Hosts position it with one name, e.g.
3
+ @layer reset, base, rori, layout, components, utilities;
4
+ Unmentioned, it lands after the host's layers (it loads later). Keep it
5
+ after the host's token layer: desk.themes overrides the host's colours. */
6
+ @layer rori {
7
+ @layer reset, tokens, base, layout, components, themes;
8
+ }
9
+
10
+ /* Rori tokens: the host's semantic tokens when it has them, standalone
11
+ defaults otherwise. Components use only --rori-*. Computed on :root, so
12
+ themes (which set the host names on :root) flow through. */
13
+ @layer rori.tokens {
14
+ :root {
15
+ --rori-gap: var(--gap, 12px);
16
+ --rori-radius: var(--radius, 10px);
17
+ --rori-radius-sm: var(--radius-sm, 6px);
18
+ --rori-ease: var(--ease, cubic-bezier(0.2, 0.8, 0.2, 1));
19
+ --rori-motion: var(--motion, 320ms);
20
+ --rori-font-sans: var(--font-sans, system-ui, -apple-system, "Segoe UI", sans-serif);
21
+ --rori-font-mono: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
22
+
23
+ --rori-canvas: var(--color-canvas, light-dark(oklch(0.95 0 0), oklch(0.14 0 0)));
24
+ --rori-surface: var(--color-surface, light-dark(oklch(0.995 0 0), oklch(0.195 0 0)));
25
+ --rori-ink: var(--color-ink, light-dark(oklch(0.2 0 0), oklch(0.94 0 0)));
26
+ --rori-ink-muted: var(--color-ink-muted, color-mix(in oklch, var(--rori-ink) 60%, var(--rori-surface)));
27
+ --rori-line: var(--color-line, color-mix(in oklch, var(--rori-ink) 14%, var(--rori-surface)));
28
+ --rori-primary: var(--color-primary, var(--rori-ink));
29
+ --rori-on-primary: var(--color-on-primary, var(--rori-surface));
30
+ --rori-success: var(--color-success, light-dark(oklch(0.55 0.12 150), oklch(0.74 0.1 150)));
31
+ --rori-danger: var(--color-danger, light-dark(oklch(0.55 0.16 27), oklch(0.7 0.13 25)));
32
+ --rori-hover: var(--color-hover, color-mix(in oklch, var(--rori-ink) 6%, transparent));
33
+ --rori-backdrop: var(--color-backdrop, color-mix(in oklch, oklch(0 0 0) 45%, transparent));
34
+ --rori-shadow: var(--shadow, 0 8px 24px color-mix(in oklch, oklch(0 0 0) 22%, transparent));
35
+ }
36
+ }
37
+
38
+ /* Just enough reset for the desk's own chrome, whatever the host resets.
39
+ :where() keeps it at zero specificity; window content is left to the host. */
40
+ @layer rori.reset {
41
+ :where(.rori-menubar, .rori-viewport, .rori-palette, .rori-terminal, .rori-shortcuts, .rori-notifications) :where(*, *::before, *::after) {
42
+ box-sizing: border-box;
43
+ }
44
+
45
+ :where(.rori-menubar, .rori-palette, .rori-terminal, .rori-win__bar, .rori-wallpaper, .rori-shortcuts, .rori-notifications) :where(button, input:not([type="checkbox"]), ul, p, dl, dd, label) {
46
+ margin: 0;
47
+ padding: 0;
48
+ border: 0;
49
+ border-radius: 0;
50
+ background: none;
51
+ color: inherit;
52
+ font: inherit;
53
+ list-style: none;
54
+ }
55
+
56
+ :where(.rori-menubar, .rori-viewport, .rori-palette, .rori-terminal) [hidden] {
57
+ display: none !important;
58
+ }
59
+ }
60
+
61
+ /* The shell owns the page: its type and colours come from the tokens (so from
62
+ the host's, when set). */
63
+ @layer rori.base {
64
+ html {
65
+ background: var(--rori-canvas);
66
+ color: var(--rori-ink);
67
+ font: 13px/1.45 var(--rori-font-sans);
68
+ }
69
+
70
+ body {
71
+ margin: 0;
72
+ }
73
+
74
+ :where(.rori-menubar, .rori-palette, .rori-terminal, .rori-wallpaper) kbd {
75
+ padding: 0 4px;
76
+ border: 1px solid var(--rori-line);
77
+ font: 12px var(--rori-font-mono);
78
+ }
79
+ }
@@ -0,0 +1,162 @@
1
+ @layer rori.layout {
2
+
3
+ /* minmax(0, 1fr): an implicit `auto` column would grow to the widest bar's
4
+ content (e.g. the unwrapped status hint) and push the viewport past the
5
+ window on narrow or zoomed screens, leaving focused columns cut off. */
6
+ /* A two-finger swipe that overscrolls the page is history navigation in
7
+ Chrome and Safari ("back" on a leftward swipe). Sideways swipes belong to
8
+ the strip, and the desk manages windows itself, so the page never lets
9
+ overscroll through — nor pull-to-refresh or rubber-banding. */
10
+ html,
11
+ body {
12
+ overscroll-behavior: none;
13
+ }
14
+
15
+ body {
16
+ height: 100vh;
17
+ display: grid;
18
+ grid-template-rows: 32px 1fr;
19
+ grid-template-columns: minmax(0, 1fr);
20
+ overflow: hidden;
21
+ }
22
+
23
+ /* Rows are assigned explicitly: other body children (turbo-cable-stream-source,
24
+ a host's own elements) fall into implicit rows after them instead of
25
+ taking the bar's row. */
26
+ .rori-menubar {
27
+ grid-row: 1;
28
+ }
29
+
30
+ .rori-viewport {
31
+ grid-row: 2;
32
+ }
33
+
34
+ /* UI › Menu bar › Bottom. */
35
+ body.bars-bottom {
36
+ grid-template-rows: 1fr 32px;
37
+ }
38
+
39
+ body.bars-bottom .rori-menubar {
40
+ grid-row: 2;
41
+ }
42
+
43
+ body.bars-bottom .rori-viewport {
44
+ grid-row: 1;
45
+ }
46
+
47
+ /* Workspaces stack vertically; each is an endless horizontal strip of columns.
48
+ rori_controller.js sets column widths and the strip/stack transforms. */
49
+ /* clip, not hidden: dialog.show() focuses the window and would scroll a
50
+ hidden-overflow box behind the transforms' back. */
51
+ .rori-viewport {
52
+ position: relative;
53
+ overflow: clip;
54
+ }
55
+
56
+ .rori-stack {
57
+ position: absolute;
58
+ inset: 0;
59
+ transform-origin: 0 0;
60
+ transition: transform var(--rori-motion) var(--rori-ease);
61
+ }
62
+
63
+ .rori-workspace {
64
+ height: 100%;
65
+ overflow: clip;
66
+ }
67
+
68
+ .is-overview .rori-workspace {
69
+ overflow: visible;
70
+ }
71
+
72
+ .rori-strip {
73
+ display: flex;
74
+ gap: var(--rori-gap);
75
+ width: max-content;
76
+ height: 100%;
77
+ padding: var(--rori-gap);
78
+ transition: transform var(--rori-motion) var(--rori-ease);
79
+ }
80
+
81
+ .rori-col {
82
+ flex: none;
83
+ display: flex;
84
+ flex-direction: column;
85
+ gap: var(--rori-gap);
86
+ min-width: 0;
87
+ transition: width var(--rori-motion) var(--rori-ease);
88
+ }
89
+
90
+ /* Loading windows wait here unseen; modals live here in the top layer. */
91
+ .rori-floating {
92
+ position: absolute;
93
+ inset: 0;
94
+ pointer-events: none;
95
+ }
96
+
97
+ .rori-floating>.rori-win:not(:modal) {
98
+ display: none;
99
+ }
100
+
101
+ .is-booting :is(.rori-stack, .rori-strip, .rori-col),
102
+ .is-scrolling .rori-strip {
103
+ transition: none;
104
+ }
105
+
106
+ .is-overview .rori-workspace {
107
+ outline: 1px dashed var(--rori-line);
108
+ outline-offset: -1px;
109
+ }
110
+
111
+ .is-overview .rori-win {
112
+ cursor: pointer;
113
+ }
114
+
115
+ .is-overview .rori-win__body {
116
+ pointer-events: none;
117
+ }
118
+
119
+ /* A centred panel, so the dismiss button sits by the text. Above the (empty)
120
+ stack so the button takes clicks; the panel itself lets them through. */
121
+ .rori-wallpaper {
122
+ position: absolute;
123
+ z-index: 1;
124
+ inset: 50% auto auto 50%;
125
+ translate: -50% -50%;
126
+ display: grid;
127
+ gap: 6px;
128
+ padding: 14px 36px;
129
+ border-radius: var(--rori-radius);
130
+ text-align: center;
131
+ color: var(--rori-ink-muted);
132
+ pointer-events: none;
133
+ }
134
+
135
+ .rori-viewport:not(.is-empty) .rori-wallpaper,
136
+ .hint-hidden .rori-wallpaper {
137
+ display: none;
138
+ }
139
+
140
+ .rori-wallpaper__dismiss {
141
+ position: absolute;
142
+ top: 6px;
143
+ right: 6px;
144
+ width: 22px;
145
+ height: 22px;
146
+ border-radius: 4px;
147
+ color: var(--rori-ink-muted);
148
+ line-height: 1;
149
+ cursor: pointer;
150
+ pointer-events: auto;
151
+ }
152
+
153
+ .rori-wallpaper__dismiss:hover {
154
+ background: var(--rori-hover);
155
+ color: var(--rori-ink);
156
+ }
157
+
158
+ .rori-wallpaper__title {
159
+ font-weight: 600;
160
+ color: var(--rori-ink);
161
+ }
162
+ }
@@ -0,0 +1,117 @@
1
+ @layer rori.components {
2
+
3
+ /* The desk's only bar: brand, workspaces, the column minimap (takes the
4
+ slack), search, shortcuts, clock. */
5
+ .rori-menubar {
6
+ display: flex;
7
+ align-items: center;
8
+ gap: 12px;
9
+ min-width: 0;
10
+ padding: 0 var(--rori-gap);
11
+ }
12
+
13
+ .rori-menubar .rori-minimap {
14
+ flex: 1;
15
+ min-width: 0;
16
+ overflow: hidden;
17
+ }
18
+
19
+ .rori-menubar__shortcuts {
20
+ flex: none;
21
+ color: var(--rori-ink-muted);
22
+ white-space: nowrap;
23
+ text-decoration: none;
24
+ cursor: pointer;
25
+ }
26
+
27
+ .rori-menubar__shortcuts:hover {
28
+ color: var(--rori-ink);
29
+ }
30
+
31
+ .rori-menubar__brand {
32
+ font: 600 12px var(--rori-font-mono);
33
+ }
34
+
35
+ .rori-menubar__search {
36
+ display: flex;
37
+ align-items: center;
38
+ gap: 24px;
39
+ height: 22px;
40
+ min-width: 18rem;
41
+ padding: 0 4px 0 10px;
42
+ border: 1px solid var(--rori-line);
43
+ border-radius: var(--rori-radius-sm);
44
+ color: var(--rori-ink-muted);
45
+ cursor: pointer;
46
+ }
47
+
48
+ .rori-menubar__search:hover {
49
+ border-color: var(--rori-ink-muted);
50
+ color: var(--rori-ink);
51
+ }
52
+
53
+ .rori-menubar__search kbd {
54
+ margin-left: auto;
55
+ }
56
+
57
+ .rori-menubar__clock {
58
+ font: 12px var(--rori-font-mono);
59
+ color: var(--rori-ink-muted);
60
+ }
61
+
62
+ .rori-workspaces {
63
+ display: flex;
64
+ gap: 4px;
65
+ }
66
+
67
+ .rori-workspace-button {
68
+ height: 18px;
69
+ padding: 0 7px;
70
+ border-radius: 4px;
71
+ font: 11px var(--rori-font-mono);
72
+ color: var(--rori-ink-muted);
73
+ white-space: nowrap;
74
+ cursor: pointer;
75
+ }
76
+
77
+ .rori-workspace-button:hover {
78
+ background: var(--rori-hover);
79
+ }
80
+
81
+ .rori-workspace-button[aria-current] {
82
+ background: var(--rori-primary);
83
+ color: var(--rori-on-primary);
84
+ }
85
+
86
+ .rori-minimap {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 4px;
90
+ }
91
+
92
+ .rori-minimap__col {
93
+ display: flex;
94
+ flex-direction: column;
95
+ gap: 2px;
96
+ height: 20px;
97
+ padding: 2px;
98
+ border: 1px solid transparent;
99
+ border-radius: 4px;
100
+ cursor: pointer;
101
+ transition: width var(--rori-motion) var(--rori-ease);
102
+ }
103
+
104
+ .rori-minimap__col.is-visible {
105
+ border-color: var(--rori-line);
106
+ }
107
+
108
+ .rori-minimap__col span {
109
+ flex: 1;
110
+ border-radius: 2px;
111
+ background: var(--rori-line);
112
+ }
113
+
114
+ .rori-minimap__col span.is-focused {
115
+ background: var(--rori-ink);
116
+ }
117
+ }