janela 0.8.0 → 0.9.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +33 -2
  4. data/UPGRADING.md +48 -0
  5. data/app/assets/javascripts/janela/frame_controller.js +15 -0
  6. data/app/assets/stylesheets/janela.css +16 -8
  7. data/app/controllers/janela/application_controller.rb +7 -0
  8. data/app/controllers/janela/panes_controller.rb +22 -7
  9. data/app/controllers/janela/queries_controller.rb +2 -1
  10. data/app/helpers/janela/frames_helper.rb +26 -7
  11. data/app/models/janela/frame.rb +20 -0
  12. data/app/models/janela/pane.rb +67 -4
  13. data/app/models/janela/query.rb +7 -2
  14. data/app/views/janela/frames/_content.html.erb +26 -0
  15. data/app/views/janela/frames/_frame.html.erb +9 -1
  16. data/app/views/janela/frames/_pane.html.erb +2 -2
  17. data/app/views/janela/panes/_content_form.html.erb +33 -0
  18. data/app/views/janela/panes/_row.html.erb +1 -1
  19. data/app/views/janela/panes/edit.html.erb +5 -1
  20. data/app/views/janela/panes/new.html.erb +6 -1
  21. data/app/views/janela/queries/_query.html.erb +15 -11
  22. data/config/locales/en.yml +5 -0
  23. data/db/migrate/20260924000001_add_content_to_janela_panes.rb +16 -0
  24. data/db/migrate/20260924000002_add_key_to_janela_frames.rb +10 -0
  25. data/docs/composing.md +269 -0
  26. data/docs/decisions/038-a-ratio-is-a-measure-of-its-own.md +194 -0
  27. data/docs/decisions/039-a-pane-can-hold-words-and-only-code-writes-markup.md +168 -0
  28. data/docs/decisions/040-a-host-can-fix-a-frames-filter.md +145 -0
  29. data/docs/decisions/041-a-host-finds-its-frame-by-owner-and-key.md +112 -0
  30. data/docs/decisions/042-a-charts-title-is-a-figcaption.md +174 -0
  31. data/docs/decisions/INDEX.md +18 -12
  32. data/docs/multi-tenancy.md +22 -0
  33. data/docs/naming.md +7 -0
  34. data/docs/roadmap.md +129 -8
  35. data/docs/theming.md +17 -15
  36. data/lib/janela/definition.rb +8 -0
  37. data/lib/janela/version.rb +1 -1
  38. metadata +25 -17
@@ -149,6 +149,28 @@ save failed silently. `bin/rails janela:doctor` reports this for you:
149
149
  it asks your policy for a scope over frames and looks for an owner in
150
150
  what comes back.
151
151
 
152
+ ### A frame of your own for a page
153
+
154
+ Your code finds a frame it keeps for one of its pages by owner and key
155
+ (ADR 041):
156
+
157
+ ```ruby
158
+ Janela::Frame.for(Current.account, :overview)
159
+ Janela::Frame.for(queue, :analytics) { |frame| frame.name = "#{queue.name} analytics" }
160
+ ```
161
+
162
+ When the owner is the tenant, nothing changes in your policy. When it is
163
+ one of your own records, such as the queue above, the frame belongs to
164
+ the tenant through that record, and your scope has to say so or the
165
+ frame is hidden from the people it was made for:
166
+
167
+ ```ruby
168
+ def resolve
169
+ scope.where(owner: Current.account)
170
+ .or(scope.where(owner_type: "Queue", owner_id: Current.account.queues.select(:id)))
171
+ end
172
+ ```
173
+
152
174
  ## Snapshots
153
175
 
154
176
  A snapshot carries the same polymorphic owner a frame does, so your
data/docs/naming.md CHANGED
@@ -44,6 +44,13 @@ from the thing itself rather than from a list of synonyms for "chart".
44
44
  | **Query** | The runtime object that calculates one pane | Not a window word, on purpose. It is an implementation detail rather than something a person arranges, so it gets the plain name for what it does. |
45
45
  | **Grid** | How a frame is divided: `columns`, `gap`, and each pane's `span` | A window is divided into panes, and the grid is the division. Small integers that choose a class the stylesheet already defines, so nothing an analyst types reaches CSS (ADR 016). |
46
46
  | **Vitral** | The optional stained glass theme | A stained glass window, in the same language. See ADR 023. |
47
+ | **Vista** | The roadmap | The view through the window. What you can see from here is also what is ahead: *outlook* and *prospect* carry the same double meaning in English, and this one carries it in Portuguese. |
48
+ | **Horizonte** | The roadmap's far half: wanted, not blocking a release | The horizon is the edge of what you can see. Work still in sight, as against work that is not in the picture at all. |
49
+
50
+ The last two are a step away from anatomy. A window's parts name the
51
+ things a host arranges; what you see through it names the things the
52
+ project has not built yet. Both halves come from the same object, which
53
+ is the point of picking an object rather than a list of synonyms.
47
54
 
48
55
  The anatomy was argued over before it was settled. *Sash* was
49
56
  considered for the dashboard and rejected: a sash is one layer inside a
data/docs/roadmap.md CHANGED
@@ -2,7 +2,125 @@
2
2
  Topics: roadmap, releases, scope, planning
3
3
  ---
4
4
 
5
- # Where Janela Is Going
5
+ # Vista
6
+
7
+ <p style="text-align: center;">Vista shows where Janela is heading.</p>
8
+
9
+ <svg viewBox="0 0 680 360" width="100%" role="img" aria-labelledby="vista-title vista-desc" class="vista-art" style="display: block; margin: 1.75rem 0; border-radius: 10px;">
10
+ <title id="vista-title">Vista</title>
11
+ <desc id="vista-desc">Sea and sky with a horizon across them. Eight lights burn on the near water, one for each issue in 1.0, and three sit far off at the horizon for the work still in sight past it. A low sun rises and sets on the horizon as the pointer moves up and down, and never climbs higher: the sky above is empty, because what is not coming is not in view.</desc>
12
+
13
+ <style>
14
+ .vista-art .v-far { transform: translate3d(calc(var(--vitral-shift-x, 0) * 13px), calc(var(--vitral-shift-y, 0) * 7px), 0); transition: transform .45s cubic-bezier(.2,.7,.3,1); }
15
+ .vista-art .v-mid { transform: translate3d(calc(var(--vitral-shift-x, 0) * 6px), calc(var(--vitral-shift-y, 0) * 3px), 0); transition: transform .45s cubic-bezier(.2,.7,.3,1); }
16
+ .vista-art .v-near { transform: translate3d(calc(var(--vitral-shift-x, 0) * -8px), calc(var(--vitral-shift-y, 0) * -4px), 0); transition: transform .45s cubic-bezier(.2,.7,.3,1); }
17
+ .vista-art .v-glow-a { opacity: calc(.6 - var(--vitral-shift-x, 0) * 1.2); transform: translate3d(calc(var(--vitral-shift-x, 0) * -40px), calc(var(--vitral-shift-y, 0) * 16px), 0); transition: transform .9s cubic-bezier(.2,.7,.3,1), opacity .9s ease; }
18
+ .vista-art .v-glow-b { opacity: calc(.6 + var(--vitral-shift-x, 0) * 1.2); transform: translate3d(calc(var(--vitral-shift-x, 0) * 44px), calc(var(--vitral-shift-y, 0) * -14px), 0); transition: transform .9s cubic-bezier(.2,.7,.3,1), opacity .9s ease; }
19
+ .vista-art .v-sun { transform: translate3d(calc(var(--vitral-shift-x, 0) * 10px), calc(var(--vitral-shift-y, 0) * 84px), 0); transition: transform 1.1s cubic-bezier(.2,.7,.3,1); }
20
+ .vista-art .v-glint { opacity: calc(.45 - var(--vitral-shift-y, 0) * .9); transform: translate3d(calc(var(--vitral-shift-x, 0) * 10px), 0, 0); transition: opacity 1.1s ease, transform 1.1s cubic-bezier(.2,.7,.3,1); }
21
+ @media (prefers-reduced-motion: reduce) {
22
+ .vista-art .v-sun, .vista-art .v-glint { transform: none; opacity: .45; }
23
+ .vista-art .v-far, .vista-art .v-mid, .vista-art .v-near, .vista-art .v-glow-a, .vista-art .v-glow-b { transform: none; opacity: .6; }
24
+ }
25
+ </style>
26
+
27
+ <defs>
28
+ <clipPath id="vFrame"><rect x="0" y="0" width="680" height="360" rx="10"/></clipPath>
29
+ <clipPath id="vAbove"><rect x="0" y="0" width="680" height="196"/></clipPath>
30
+ <radialGradient id="vSun">
31
+ <stop offset="0" stop-color="#FFF4D6" stop-opacity="0.95"/>
32
+ <stop offset="0.7" stop-color="#FFD58A" stop-opacity="0.9"/>
33
+ <stop offset="1" stop-color="#F7A96B" stop-opacity="0.75"/>
34
+ </radialGradient>
35
+ <radialGradient id="vSunHalo">
36
+ <stop offset="0" stop-color="#FFD98F" stop-opacity="0.5"/>
37
+ <stop offset="1" stop-color="#F7A96B" stop-opacity="0"/>
38
+ </radialGradient>
39
+ <linearGradient id="vSky" x1="0" y1="0" x2="0.22" y2="1">
40
+ <stop offset="0" stop-color="#5B49C9" stop-opacity="0.54"/>
41
+ <stop offset="0.38" stop-color="#8A6EE4" stop-opacity="0.34"/>
42
+ <stop offset="0.7" stop-color="#E06082" stop-opacity="0.29"/>
43
+ <stop offset="1" stop-color="#F0B046" stop-opacity="0.46"/>
44
+ </linearGradient>
45
+ <linearGradient id="vSea" x1="0" y1="0" x2="0.08" y2="1">
46
+ <stop offset="0" stop-color="#F0B046" stop-opacity="0.34"/>
47
+ <stop offset="0.22" stop-color="#1CA89E" stop-opacity="0.38"/>
48
+ <stop offset="1" stop-color="#132F5C" stop-opacity="0.78"/>
49
+ </linearGradient>
50
+ <radialGradient id="vLamp">
51
+ <stop offset="0" stop-color="#FFE9B8" stop-opacity="0.78"/>
52
+ <stop offset="1" stop-color="#FFD98F" stop-opacity="0"/>
53
+ </radialGradient>
54
+ <radialGradient id="vP1"><stop offset="0" stop-color="#367AEB" stop-opacity="0.6"/><stop offset="1" stop-color="#367AEB" stop-opacity="0"/></radialGradient>
55
+ <radialGradient id="vP2"><stop offset="0" stop-color="#1CA89E" stop-opacity="0.30"/><stop offset="1" stop-color="#1CA89E" stop-opacity="0"/></radialGradient>
56
+ <radialGradient id="vP3"><stop offset="0" stop-color="#E06082" stop-opacity="0.5"/><stop offset="1" stop-color="#E06082" stop-opacity="0"/></radialGradient>
57
+ </defs>
58
+
59
+ <g clip-path="url(#vFrame)">
60
+ <g class="v-far">
61
+ <rect x="-30" y="-30" width="740" height="256" fill="url(#vSky)"/>
62
+ <ellipse class="v-glow-a" cx="150" cy="104" rx="230" ry="132" fill="url(#vP1)"/>
63
+ <ellipse class="v-glow-b" cx="520" cy="140" rx="250" ry="100" fill="url(#vP3)"/>
64
+ </g>
65
+
66
+ <g clip-path="url(#vAbove)">
67
+ <g class="v-sun">
68
+ <circle cx="432" cy="192" r="96" fill="url(#vSunHalo)"/>
69
+ <circle cx="432" cy="192" r="24" fill="url(#vSun)"/>
70
+ </g>
71
+ </g>
72
+ <g class="v-mid">
73
+ <rect x="-30" y="196" width="740" height="204" fill="url(#vSea)"/>
74
+ <ellipse cx="580" cy="288" rx="220" ry="124" fill="url(#vP2)"/>
75
+ <path d="M6 200.0 h18 M46 200.0 h18 M80 200.0 h11 M145 200.0 h9 M173 200.0 h8 M204 200.0 h22 M235 200.0 h7 M266 200.0 h18 M305 200.0 h19 M339 200.0 h23 M372 200.0 h30 M424 200.0 h24 M490 200.0 h16 M519 200.0 h30 M555 200.0 h12 M581 200.0 h30 M618 200.0 h22 M650 200.0 h9" stroke="#FFF3DA" stroke-opacity="0.28" stroke-width="1" stroke-linecap="round"/>
76
+ <path d="M74 204.2 h12 M100 204.2 h12 M195 204.2 h28 M245 204.2 h13 M384 204.2 h10 M404 204.2 h23 M441 204.2 h31 M527 204.2 h30 M568 204.2 h12 M604 204.2 h12 M639 204.2 h23 M669 204.2 h8" stroke="#FFF3DA" stroke-opacity="0.28" stroke-width="1" stroke-linecap="round"/>
77
+ <path d="M127 209.4 h23 M169 209.4 h16 M278 209.4 h32 M329 209.4 h27 M398 209.4 h27 M431 209.4 h25 M477 209.4 h17 M501 209.4 h23 M548 209.4 h28 M598 209.4 h33 M650 209.4 h32" stroke="#FFF3DA" stroke-opacity="0.27" stroke-width="1" stroke-linecap="round"/>
78
+ <path d="M6 215.8 h32 M56 215.8 h27 M148 215.8 h38 M208 215.8 h20 M247 215.8 h22 M370 215.8 h23 M487 215.8 h35 M538 215.8 h35 M593 215.8 h15 M631 215.8 h36" stroke="#FFF3DA" stroke-opacity="0.26" stroke-width="1" stroke-linecap="round"/>
79
+ <path d="M6 223.7 h28 M44 223.7 h26 M96 223.7 h32 M141 223.7 h15 M169 223.7 h17 M207 223.7 h26 M258 223.7 h41 M308 223.7 h11 M326 223.7 h32 M492 223.7 h30 M543 223.7 h31" stroke="#FFF3DA" stroke-opacity="0.25" stroke-width="1" stroke-linecap="round"/>
80
+ <path d="M56 233.5 h23 M99 233.5 h33 M149 233.5 h39 M197 233.5 h44 M252 233.5 h27 M308 233.5 h24 M361 233.5 h39 M477 233.5 h40 M542 233.5 h18 M591 233.5 h46 M664 233.5 h22" stroke="#FFF3DA" stroke-opacity="0.24" stroke-width="1" stroke-linecap="round"/>
81
+ <path d="M52 245.5 h34 M107 245.5 h13 M246 245.5 h33 M309 245.5 h40 M494 245.5 h38 M562 245.5 h35 M615 245.5 h46" stroke="#FFF3DA" stroke-opacity="0.22" stroke-width="1" stroke-linecap="round"/>
82
+ <path d="M6 260.4 h52 M147 260.4 h37 M193 260.4 h58 M271 260.4 h50 M414 260.4 h33 M482 260.4 h26 M521 260.4 h33 M589 260.4 h35 M639 260.4 h42" stroke="#FFF3DA" stroke-opacity="0.20" stroke-width="1" stroke-linecap="round"/>
83
+ <path d="M79 278.8 h28 M126 278.8 h34 M310 278.8 h39 M432 278.8 h49 M507 278.8 h60 M605 278.8 h28 M669 278.8 h63" stroke="#FFF3DA" stroke-opacity="0.17" stroke-width="1" stroke-linecap="round"/>
84
+ <path d="M296 301.6 h69 M404 301.6 h26 M521 301.6 h73 M610 301.6 h49" stroke="#FFF3DA" stroke-opacity="0.14" stroke-width="1" stroke-linecap="round"/>
85
+ <path d="M156 329.6 h58 M314 329.6 h91 M422 329.6 h26 M479 329.6 h75 M587 329.6 h58" stroke="#FFF3DA" stroke-opacity="0.09" stroke-width="1" stroke-linecap="round"/>
86
+ <circle cx="186" cy="202" r="8" fill="url(#vLamp)" opacity="0.2"/>
87
+ <circle cx="186" cy="202" r="1.7" fill="#FFF3DC" opacity="0.55"/>
88
+ <circle cx="322" cy="201" r="8" fill="url(#vLamp)" opacity="0.2"/>
89
+ <circle cx="322" cy="201" r="1.5" fill="#FFF3DC" opacity="0.55"/>
90
+ <circle cx="540" cy="202.5" r="8" fill="url(#vLamp)" opacity="0.2"/>
91
+ <circle cx="540" cy="202.5" r="1.6" fill="#FFF3DC" opacity="0.55"/>
92
+ </g>
93
+
94
+ <g class="v-glint" stroke="#FFE2A6" stroke-linecap="round">
95
+ <path d="M404 201 h56" stroke-width="2" stroke-opacity="0.7"/>
96
+ <path d="M412 207 h40" stroke-width="1.6" stroke-opacity="0.55"/>
97
+ <path d="M418 214 h28" stroke-width="1.4" stroke-opacity="0.42"/>
98
+ <path d="M423 223 h18" stroke-width="1.2" stroke-opacity="0.3"/>
99
+ <path d="M427 234 h10" stroke-width="1" stroke-opacity="0.2"/>
100
+ </g>
101
+ <g class="v-near">
102
+ <circle cx="74" cy="330" r="22" fill="url(#vLamp)" opacity="0.4"/>
103
+ <circle cx="74" cy="330" r="4.3" fill="#FFF6E2" opacity="0.88"/>
104
+ <circle cx="154" cy="302" r="20" fill="url(#vLamp)" opacity="0.4"/>
105
+ <circle cx="154" cy="302" r="4.0" fill="#FFF6E2" opacity="0.88"/>
106
+ <circle cx="238" cy="340" r="21" fill="url(#vLamp)" opacity="0.4"/>
107
+ <circle cx="238" cy="340" r="4.2" fill="#FFF6E2" opacity="0.88"/>
108
+ <circle cx="312" cy="283" r="18" fill="url(#vLamp)" opacity="0.4"/>
109
+ <circle cx="312" cy="283" r="3.6" fill="#FFF6E2" opacity="0.88"/>
110
+ <circle cx="392" cy="314" r="19" fill="url(#vLamp)" opacity="0.4"/>
111
+ <circle cx="392" cy="314" r="3.8" fill="#FFF6E2" opacity="0.88"/>
112
+ <circle cx="470" cy="268" r="16" fill="url(#vLamp)" opacity="0.4"/>
113
+ <circle cx="470" cy="268" r="3.3" fill="#FFF6E2" opacity="0.88"/>
114
+ <circle cx="552" cy="294" r="18" fill="url(#vLamp)" opacity="0.4"/>
115
+ <circle cx="552" cy="294" r="3.5" fill="#FFF6E2" opacity="0.88"/>
116
+ <circle cx="630" cy="256" r="16" fill="url(#vLamp)" opacity="0.4"/>
117
+ <circle cx="630" cy="256" r="3.1" fill="#FFF6E2" opacity="0.88"/>
118
+ </g>
119
+
120
+ <path d="M0 196 H680" stroke="#FFF1D2" stroke-opacity="0.72" stroke-width="1.5" fill="none"/>
121
+ <text x="664" y="218" text-anchor="end" font-size="10" letter-spacing="0.13em" fill="#FFF4DE" opacity="0.6">HORIZONTE</text>
122
+ </g>
123
+ </svg>
6
124
 
7
125
  Janela is alpha. It works, it is tested against a real Rails application
8
126
  in a real browser, and its public surface has changed in three of the
@@ -39,6 +157,8 @@ A check nobody can prove is working is a check nobody should rely on.
39
157
 
40
158
  ## In 1.0
41
159
 
160
+ The eight lights burning in the near ground.
161
+
42
162
  | Issue | What |
43
163
  | --- | --- |
44
164
  | [#24](https://github.com/retail-tasker/janela/issues/24) | A chart's height and aspect ratio are the host's to set |
@@ -53,10 +173,10 @@ A check nobody can prove is working is a check nobody should rely on.
53
173
  Progress is tracked on the
54
174
  [1.0 milestone](https://github.com/retail-tasker/janela/milestone/2).
55
175
 
56
- ## After 1.0
176
+ ## Horizonte
57
177
 
58
- Wanted, not blocking a stable release. Being on this list is not a
59
- refusal.
178
+ The three lights far off at the horizon. Wanted, not blocking a stable
179
+ release, and being on this list is not a refusal.
60
180
 
61
181
  - **Drill-down on time panes** ([#18](https://github.com/retail-tasker/janela/issues/18)).
62
182
  Clicking a month could filter every other pane to it, or narrow that
@@ -70,10 +190,11 @@ refusal.
70
190
 
71
191
  ## Not coming
72
192
 
73
- Janela is meant to be small enough that forking it and adding your own
74
- piece is a normal way to use it. These are the things you would be
75
- adding yourself, and each is left out because something you already run
76
- does it better.
193
+ The sky above the horizon is empty, and that is the honest part of the
194
+ picture. Janela is meant to be small enough that forking it and adding
195
+ your own piece is a normal way to use it. These are the things you would
196
+ be adding yourself, and each is left out because something you already
197
+ run does it better.
77
198
 
78
199
  Natural-language query. A separate data warehouse. A row-level-security
79
200
  subsystem, because your application already has Pundit or CanCanCan and
data/docs/theming.md CHANGED
@@ -64,22 +64,27 @@ the names a theme spends most of its time on.
64
64
 
65
65
  | Class | On | Rendered when |
66
66
  | --- | --- | --- |
67
- | `janela-pane` | `<table>`, `<p>` or `<canvas>` | every pane, whatever the renderer |
67
+ | `janela-pane` | `<table>`, `<p>`, `<figure>` or `<div>` | every pane, whatever the renderer |
68
68
  | `janela-value` | `<p>` | a single value pane |
69
69
  | `janela-value-label` | `<span>` | its caption |
70
70
  | `janela-value-number` | `<strong>` | the number itself |
71
- | `janela-chart` | `<canvas>` | a bar or line pane |
71
+ | `janela-chart` | `<canvas>` | a bar or line pane, inside its `<figure>` |
72
+ | `janela-chart-title` | `<figcaption>` | its caption (ADR 042) |
72
73
  | `janela-empty` | `<p>` | a pane whose query returned nothing |
73
74
  | `janela-error` | `<p>` | a pane that could not be read |
75
+ | `janela-content` | `<div>` | a stored pane holding words or a host partial rather than a query (ADR 039) |
76
+ | `janela-content-heading` | `<h2>` | a text pane's heading |
74
77
 
75
- A table pane also renders a `<caption>`, its accessible name, and a
76
- chart pane carries the same string as `aria-label`.
78
+ A table pane renders a `<caption>` and a chart pane a `<figcaption>`,
79
+ each its own accessible name; the chart's canvas points to its
80
+ figcaption with `aria-labelledby` rather than repeating the string in
81
+ `aria-label`, so the two cannot drift apart (ADR 042).
77
82
 
78
83
  ### Hiding a heading you already wrote
79
84
 
80
- Put `janela-own-headings` on any ancestor and a pane's caption and a
81
- single value's label are hidden from sight while staying in the
82
- accessibility tree.
85
+ Put `janela-own-headings` on any ancestor and a table's caption, a
86
+ single value's label and a chart's title are hidden from sight while
87
+ staying in the accessibility tree.
83
88
 
84
89
  ```erb
85
90
  <div class="janela-own-headings">
@@ -89,14 +94,11 @@ accessibility tree.
89
94
  ```
90
95
 
91
96
  Use it when your own markup already says what the pane is, so the text
92
- is not on screen twice. It hides rather than removes on purpose: a table
93
- with no caption has no accessible name, so a screen reader lands on a
94
- grid of numbers with nothing to say what they measure. `display: none`
95
- would do that, which is why this rule ships here rather than being left
96
- for each host to write.
97
-
98
- A chart pane needs nothing: its title is only ever an `aria-label` and
99
- was never drawn.
97
+ is not on screen twice. It hides rather than removes on purpose: a pane
98
+ with no visible caption still needs its accessible name, so a screen
99
+ reader lands on a grid of numbers with nothing to say what they
100
+ measure. `display: none` would do that, which is why this rule ships
101
+ here rather than being left for each host to write.
100
102
 
101
103
  ## What is not the contract
102
104
 
@@ -97,6 +97,14 @@ module Janela
97
97
  dimensions.values.filter_map(&:through).map(&:to_s).uniq
98
98
  end
99
99
 
100
+ # A filter the host fixed for a render (ADR 040), applied as its own
101
+ # condition before the reader's, so the reader's can only narrow inside
102
+ # it. The same bounds as any filter: declared dimensions, ADR 025's
103
+ # predicates and value limit.
104
+ def narrow(relation, conditions)
105
+ filter(relation, conditions.to_h)
106
+ end
107
+
100
108
  private
101
109
  def filter(relation, params)
102
110
  return relation if params.empty?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Janela
4
- VERSION = "0.8.0"
4
+ VERSION = "0.9.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: janela
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jay Killeen
@@ -114,6 +114,7 @@ files:
114
114
  - app/models/janela/query.rb
115
115
  - app/models/janela/snapshot.rb
116
116
  - app/views/janela/frames/_card.html.erb
117
+ - app/views/janela/frames/_content.html.erb
117
118
  - app/views/janela/frames/_form.html.erb
118
119
  - app/views/janela/frames/_frame.html.erb
119
120
  - app/views/janela/frames/_pane.html.erb
@@ -121,6 +122,7 @@ files:
121
122
  - app/views/janela/frames/index.html.erb
122
123
  - app/views/janela/frames/new.html.erb
123
124
  - app/views/janela/frames/show.html.erb
125
+ - app/views/janela/panes/_content_form.html.erb
124
126
  - app/views/janela/panes/_form.html.erb
125
127
  - app/views/janela/panes/_row.html.erb
126
128
  - app/views/janela/panes/edit.html.erb
@@ -137,6 +139,9 @@ files:
137
139
  - db/migrate/20260916000001_create_janela_frames.rb
138
140
  - db/migrate/20260916000002_create_janela_panes.rb
139
141
  - db/migrate/20260921000001_add_owner_to_janela_snapshots.rb
142
+ - db/migrate/20260924000001_add_content_to_janela_panes.rb
143
+ - db/migrate/20260924000002_add_key_to_janela_frames.rb
144
+ - docs/composing.md
140
145
  - docs/decisions/001-built-to-be-forked.md
141
146
  - docs/decisions/002-measures-and-dimensions-over-ransack.md
142
147
  - docs/decisions/003-cross-filtering-with-turbo-frames.md
@@ -174,6 +179,11 @@ files:
174
179
  - docs/decisions/035-a-check-does-what-janela-does-or-says-what-it-saw.md
175
180
  - docs/decisions/036-janela-publishes-what-a-theme-may-target.md
176
181
  - docs/decisions/037-what-1-0-means.md
182
+ - docs/decisions/038-a-ratio-is-a-measure-of-its-own.md
183
+ - docs/decisions/039-a-pane-can-hold-words-and-only-code-writes-markup.md
184
+ - docs/decisions/040-a-host-can-fix-a-frames-filter.md
185
+ - docs/decisions/041-a-host-finds-its-frame-by-owner-and-key.md
186
+ - docs/decisions/042-a-charts-title-is-a-figcaption.md
177
187
  - docs/decisions/INDEX.md
178
188
  - docs/multi-tenancy.md
179
189
  - docs/naming.md
@@ -199,24 +209,22 @@ metadata:
199
209
  bug_tracker_uri: https://github.com/retail-tasker/janela/issues
200
210
  rubygems_mfa_required: 'true'
201
211
  post_install_message: |
202
- Janela 0.8.0 raises where it used to do nothing. One thing to act on,
203
- and one to read.
212
+ Janela 0.9.0: a migration if you use stored frames, and a markup
213
+ check if you wrote CSS or JavaScript against a chart pane.
204
214
 
205
- 1. If you set Janela.parent_controller anywhere but an initializer, it
206
- now raises instead of being ignored in silence. config.to_prepare
207
- and config.after_initialize are both too late, and the README's own
208
- layout recipe is a to_prepare block. Until now your dashboards kept
209
- inheriting whatever was named first, so your authentication and
210
- policy_scope were not the ones you wrote (ADR 035).
215
+ 1. If you use stored frames, take two migrations together:
216
+ bin/rails janela:install:migrations && bin/rails db:migrate
217
+ A pane can now hold words or a host partial instead of a query
218
+ (ADR 039), and Janela::Frame.for(owner, key) finds a frame kept
219
+ for one of your pages without a column of your own (ADR 041).
220
+ Existing frames and panes are unaffected either way.
211
221
 
212
- 2. bin/rails janela:doctor reports findings it could not reach before,
213
- including a Pundit host with no policy for Janela::Frame or
214
- Janela::Snapshot, which used to pass the doctor and raise on every
215
- request. Expect things that were always true and never printed.
216
-
217
- Also: the class names on Janela's own pages are no longer public API.
218
- They are scoped under janela-page and may change in any release. What
219
- your own markup contains is unchanged and is listed in docs/theming.md.
222
+ 2. A chart pane's title is now a visible <figcaption> inside a
223
+ <figure> wrapping the canvas, not only the canvas's aria-label
224
+ (ADR 042). janela-pane moved from the <canvas> to the <figure>;
225
+ janela-chart stayed on the canvas. If you selected
226
+ .janela-pane.janela-chart as one element, or read a chart's
227
+ title from aria-label, both need updating.
220
228
 
221
229
  Steps: UPGRADING.md in this gem, or
222
230
  https://github.com/retail-tasker/janela/blob/main/UPGRADING.md