janela 0.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/README.md +36 -5
- data/UPGRADING.md +111 -0
- data/app/assets/javascripts/janela/frame_controller.js +15 -0
- data/app/assets/stylesheets/janela.css +29 -0
- data/app/controllers/janela/application_controller.rb +7 -0
- data/app/controllers/janela/panes_controller.rb +22 -7
- data/app/controllers/janela/queries_controller.rb +2 -1
- data/app/helpers/janela/frames_helper.rb +26 -7
- data/app/models/janela/frame.rb +20 -0
- data/app/models/janela/pane.rb +67 -4
- data/app/models/janela/query.rb +7 -2
- data/app/views/janela/frames/_content.html.erb +26 -0
- data/app/views/janela/frames/_frame.html.erb +9 -1
- data/app/views/janela/frames/_pane.html.erb +2 -2
- data/app/views/janela/panes/_content_form.html.erb +33 -0
- data/app/views/janela/panes/_row.html.erb +1 -1
- data/app/views/janela/panes/edit.html.erb +5 -1
- data/app/views/janela/panes/new.html.erb +6 -1
- data/app/views/janela/queries/_query.html.erb +15 -11
- data/config/locales/en.yml +5 -0
- data/db/migrate/20260924000001_add_content_to_janela_panes.rb +16 -0
- data/db/migrate/20260924000002_add_key_to_janela_frames.rb +10 -0
- data/docs/composing.md +269 -0
- data/docs/decisions/032-janela-will-not-read-a-model-it-cannot-scope.md +1 -0
- data/docs/decisions/035-a-check-does-what-janela-does-or-says-what-it-saw.md +262 -0
- data/docs/decisions/036-janela-publishes-what-a-theme-may-target.md +171 -0
- data/docs/decisions/037-what-1-0-means.md +172 -0
- data/docs/decisions/038-a-ratio-is-a-measure-of-its-own.md +194 -0
- data/docs/decisions/039-a-pane-can-hold-words-and-only-code-writes-markup.md +168 -0
- data/docs/decisions/040-a-host-can-fix-a-frames-filter.md +145 -0
- data/docs/decisions/041-a-host-finds-its-frame-by-owner-and-key.md +112 -0
- data/docs/decisions/042-a-charts-title-is-a-figcaption.md +174 -0
- data/docs/decisions/INDEX.md +26 -15
- data/docs/multi-tenancy.md +22 -0
- data/docs/naming.md +7 -0
- data/docs/roadmap.md +218 -0
- data/docs/theming.md +179 -0
- data/lib/janela/definition.rb +16 -1
- data/lib/janela/doctor.rb +121 -32
- data/lib/janela/model.rb +6 -1
- data/lib/janela/version.rb +1 -1
- data/lib/janela.rb +47 -3
- metadata +30 -15
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
Date: 2026-09-28
|
|
3
|
+
Status: Accepted
|
|
4
|
+
Related: ADR 016, ADR 018, ADR 024, ADR 026, ADR 036
|
|
5
|
+
Triggers:
|
|
6
|
+
- adding a title, caption or visible label to any renderer
|
|
7
|
+
- changing what a chart pane's markup contains, or what carries its classes
|
|
8
|
+
- extending or reading `.janela-own-headings`
|
|
9
|
+
- deciding what a theme may target in `janela.css` (ADR 036)
|
|
10
|
+
- a chart type that cannot sit inside a figure the way bar and line do
|
|
11
|
+
Topics: rendering, charts, accessibility, styling, public-api, theming
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# ADR 042: A Chart's Title Is a Figcaption, and the Canvas Points to It
|
|
15
|
+
|
|
16
|
+
## Context
|
|
17
|
+
|
|
18
|
+
#61 found that a chart pane (`renderer: bar` or `renderer: line`) never
|
|
19
|
+
shows its title to a sighted reader. `janela/queries/_query.html.erb`
|
|
20
|
+
renders it as a bare `<canvas role="img" aria-label="<%= query.title
|
|
21
|
+
%>">`, and `chart_controller.js` passes the same string only into the
|
|
22
|
+
dataset's `label`, with the legend that would draw it turned off. A
|
|
23
|
+
table pane's `<caption>` and a single value pane's `<span
|
|
24
|
+
class="janela-value-label">` both render the same `query.title` as
|
|
25
|
+
visible text. A chart pane is the one renderer that does not.
|
|
26
|
+
|
|
27
|
+
This is not new. #26's own investigation, closed and shipped in 0.8.0,
|
|
28
|
+
already found and recorded the same fact while building
|
|
29
|
+
`.janela-own-headings`:
|
|
30
|
+
|
|
31
|
+
> The table's and the single value's are visible ... The chart's is
|
|
32
|
+
> not ... A chart pane is already announced without being seen.
|
|
33
|
+
|
|
34
|
+
#26 used that as the *model* the other two renderers should be brought
|
|
35
|
+
into line with for hiding purposes, not as a gap to close, and the
|
|
36
|
+
comment above `.janela-own-headings` in `janela.css` still says so:
|
|
37
|
+
"a chart pane needs nothing: its title was only ever an aria-label."
|
|
38
|
+
That sentence is accurate about the code and wrong about the design.
|
|
39
|
+
Nobody decided a chart should be the one renderer with no visible
|
|
40
|
+
title; it fell out of `legend: { display: false }` turning off the
|
|
41
|
+
only thing that would have drawn it.
|
|
42
|
+
|
|
43
|
+
### Why the issue's own fix does not hold up
|
|
44
|
+
|
|
45
|
+
#61 proposes `plugins.title: { display: true, text: this.titleValue }`,
|
|
46
|
+
Chart.js's own title option. It works, and it was rejected, for three
|
|
47
|
+
reasons that all come from the same place: the text would live inside
|
|
48
|
+
the canvas bitmap rather than in the document.
|
|
49
|
+
|
|
50
|
+
- **It cannot be hidden by `.janela-own-headings`.** That rule hides
|
|
51
|
+
the table's `<caption>` and the value's `<span>` visually while
|
|
52
|
+
leaving them in the accessibility tree, by targeting real elements
|
|
53
|
+
with CSS (#26). A host with its own heading above a chart pane would
|
|
54
|
+
have no way to suppress the canvas-drawn one short of reconfiguring
|
|
55
|
+
Chart.js, so the one mechanism #26 shipped for this exact problem
|
|
56
|
+
would not cover the one renderer it was modelled on.
|
|
57
|
+
- **It contradicts ADR 026 directly**, not just in spirit. That
|
|
58
|
+
decision's third numbered point is "every renderer degrades to
|
|
59
|
+
something readable with no JavaScript, because a pane is rendered on
|
|
60
|
+
the server before anything runs." A title Chart.js paints is not
|
|
61
|
+
there until the chart runtime has loaded and `connect()` has run;
|
|
62
|
+
server-rendered text is there in the same response that draws the
|
|
63
|
+
rest of the pane.
|
|
64
|
+
- **It is not text.** Not selectable, not found by the browser's find
|
|
65
|
+
bar, not sized by the vitral theme's typography, not read by a
|
|
66
|
+
screen reader that has switched off image descriptions but still
|
|
67
|
+
reads a page's headings.
|
|
68
|
+
|
|
69
|
+
None of that is a knock on Chart.js. It is the same argument ADR 026
|
|
70
|
+
already settled for the chart itself: a renderer draws in HTML unless
|
|
71
|
+
it genuinely cannot, and a caption is not a case a canvas is needed for.
|
|
72
|
+
|
|
73
|
+
### Where the title should live
|
|
74
|
+
|
|
75
|
+
A table pane already pairs a `<table>` with a `<caption>`. HTML has the
|
|
76
|
+
matching pair for an image with a caption: `<figure>` and
|
|
77
|
+
`<figcaption>`. Wrapping the existing canvas in one costs nothing Chart.js
|
|
78
|
+
cares about, since `new Chart(this.element, ...)` still receives the
|
|
79
|
+
canvas itself, unchanged.
|
|
80
|
+
|
|
81
|
+
One thing about that pairing had to be checked rather than assumed:
|
|
82
|
+
`<figcaption>` gives its text to the `<figure>` as a description, not
|
|
83
|
+
to an arbitrary element nested inside it. A screen reader is not
|
|
84
|
+
guaranteed to treat the figcaption as the canvas's own accessible name
|
|
85
|
+
just because it sits beside it. The canvas needs an explicit
|
|
86
|
+
`aria-labelledby` pointing at the figcaption's id; the implicit
|
|
87
|
+
figure/figcaption pairing is not enough on its own and this record
|
|
88
|
+
does not lean on it.
|
|
89
|
+
|
|
90
|
+
### What was considered
|
|
91
|
+
|
|
92
|
+
**Chart.js's `plugins.title`**, #61's own suggestion. Rejected above.
|
|
93
|
+
|
|
94
|
+
**A bare sibling, `<div class="janela-chart-title">` before an
|
|
95
|
+
unwrapped canvas.** Considered and rejected only because HTML already
|
|
96
|
+
has the element built for exactly this pairing, and using it costs
|
|
97
|
+
nothing: a chart pane on its own page already reads as a captioned
|
|
98
|
+
figure to a browser's own outline, not just to Janela's CSS.
|
|
99
|
+
|
|
100
|
+
**Leaving `aria-label` on the canvas alongside the new visible text.**
|
|
101
|
+
Rejected: two independent strings for the same title can drift, and
|
|
102
|
+
`aria-labelledby` pointing at the one that is now visible cannot.
|
|
103
|
+
|
|
104
|
+
## Decision
|
|
105
|
+
|
|
106
|
+
**A chart pane's title is a `<figcaption>` inside a `<figure>` that
|
|
107
|
+
wraps the canvas, and the canvas's accessible name is
|
|
108
|
+
`aria-labelledby` pointing at it.**
|
|
109
|
+
|
|
110
|
+
```erb
|
|
111
|
+
<figure class="janela-pane">
|
|
112
|
+
<figcaption class="janela-chart-title" id="<%= title_id %>"><%= query.title %></figcaption>
|
|
113
|
+
<canvas class="janela-chart"
|
|
114
|
+
data-controller="janela--chart"
|
|
115
|
+
...
|
|
116
|
+
role="img" aria-labelledby="<%= title_id %>"></canvas>
|
|
117
|
+
</figure>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**`janela-pane` moves from the canvas to the figure; `janela-chart`
|
|
121
|
+
stays on the canvas.** The figure is now the grid item and the thing a
|
|
122
|
+
theme styles as a pane, the same job `<table class="janela-pane">` and
|
|
123
|
+
`<p class="janela-pane janela-value">` already do for the other two
|
|
124
|
+
renderers. The canvas keeps `janela-chart` because that is what
|
|
125
|
+
`canvas.janela-chart { width: 100% !important; max-height: 20rem; }`
|
|
126
|
+
and the Stimulus controller both already target, and because #61's own
|
|
127
|
+
system test selects `canvas.janela-chart` directly; nothing that
|
|
128
|
+
selects the canvas by that class alone stops matching. What stops
|
|
129
|
+
matching is anything that selected `.janela-pane.janela-chart` as one
|
|
130
|
+
element, which is the breaking part of this change.
|
|
131
|
+
|
|
132
|
+
**`janela-chart-title` joins the pane primitives on the contract**
|
|
133
|
+
(ADR 036), the same group `janela-value-label` and a table's `caption`
|
|
134
|
+
selector are already in. Its base legibility rule (size, weight,
|
|
135
|
+
spacing) goes in `janela.css` next to the other two, not in vitral,
|
|
136
|
+
for the same reason theirs does: a pane has to be legible with no
|
|
137
|
+
theme installed.
|
|
138
|
+
|
|
139
|
+
**`.janela-own-headings` gets a third rule.** It already hides the
|
|
140
|
+
table's caption and the value's label visually while keeping them in
|
|
141
|
+
the accessibility tree; `figcaption.janela-chart-title` joins that
|
|
142
|
+
selector, and the comment above it that says a chart pane needs
|
|
143
|
+
nothing is corrected to say why it no longer does.
|
|
144
|
+
|
|
145
|
+
**Chart.js's dataset `label` and the disabled legend are unchanged.**
|
|
146
|
+
The visible title no longer depends on either, which is the point:
|
|
147
|
+
it is there whether or not the chart runtime ever finishes loading.
|
|
148
|
+
|
|
149
|
+
## Consequences
|
|
150
|
+
|
|
151
|
+
- A chart pane finally shows its title the way the other two renderers
|
|
152
|
+
do, closing #61 and the half of #26 that was left standing on
|
|
153
|
+
purpose.
|
|
154
|
+
- Breaking change: `janela-pane` is no longer on the `<canvas>`, and
|
|
155
|
+
`aria-label` is replaced by `aria-labelledby`. Anything a host wrote
|
|
156
|
+
against either goes in `UPGRADING.md` (ADR 015), and the doctor's
|
|
157
|
+
list of things to check for an old release is worth a line.
|
|
158
|
+
- `docs/theming.md` gains `janela-chart-title` in the pane primitives
|
|
159
|
+
table (ADR 036), and `janela.css` gains its rule and its line in
|
|
160
|
+
`.janela-own-headings`.
|
|
161
|
+
- The system test in `test/system/chart_test.rb` asserts
|
|
162
|
+
`canvas.janela-chart[aria-label='...']` today; it moves to asserting
|
|
163
|
+
the figcaption's text and the `aria-labelledby` wiring, which is
|
|
164
|
+
build work, not a decision.
|
|
165
|
+
- Chart.js keeps drawing bar and line exactly as it does today; nothing
|
|
166
|
+
about `chart_controller.js`'s own options needs to change for this,
|
|
167
|
+
only the markup around it.
|
|
168
|
+
- What would change this decision: a chart type that cannot sit inside
|
|
169
|
+
a figure the way bar and line do, or a case where wrapping the canvas
|
|
170
|
+
measurably breaks Chart.js's own sizing against its parent. Neither
|
|
171
|
+
is expected, since the canvas already sits one level inside a turbo
|
|
172
|
+
frame today and gains only one more ordinary block-level ancestor,
|
|
173
|
+
but it is exactly the kind of assumption to re-check against the demo
|
|
174
|
+
before the build lands rather than after.
|
data/docs/decisions/INDEX.md
CHANGED
|
@@ -20,26 +20,29 @@ them when in doubt: `grep -l "Triggers:.*fork" docs/decisions/*.md`.
|
|
|
20
20
|
|
|
21
21
|
| Topic | ADRs |
|
|
22
22
|
|-------|------|
|
|
23
|
-
| **Vision, scope, forkability** | 001, 010, 012 |
|
|
24
|
-
| **Open-source & host-decoupling** | 001, 022 |
|
|
25
|
-
| **DSL & query layer** | 002, 006, 007, 020, 025 |
|
|
23
|
+
| **Vision, scope, forkability** | 001, 010, 012, 037 |
|
|
24
|
+
| **Open-source & host-decoupling** | 001, 022, 036, 041 |
|
|
25
|
+
| **DSL & query layer** | 002, 006, 007, 020, 025, 038 |
|
|
26
26
|
| **Dependencies** | 002, 003, 004, 006, 017, 025 |
|
|
27
|
-
| **Authorisation** | 002, 003, 004, 009, 017, 019, 022, 032, 033, 034 |
|
|
27
|
+
| **Authorisation** | 002, 003, 004, 009, 017, 019, 022, 032, 033, 034, 035, 039, 040 |
|
|
28
28
|
| **Performance & storage** | 007, 017, 025 |
|
|
29
|
-
| **
|
|
30
|
-
| **
|
|
31
|
-
| **
|
|
32
|
-
| **
|
|
33
|
-
| **
|
|
34
|
-
| **
|
|
29
|
+
| **Ordering & formatting** | 007, 020, 038 |
|
|
30
|
+
| **Cross-filtering & Hotwire** | 003, 004, 005, 008, 024, 025, 040 |
|
|
31
|
+
| **Layouts & views** | 011, 012, 016, 018, 020, 027, 039 |
|
|
32
|
+
| **CSS & styling** | 016, 018, 023, 026, 027, 036, 042 |
|
|
33
|
+
| **Frames, panes & persistence** | 012, 013, 014, 019, 029, 030, 033, 039, 040, 041 |
|
|
34
|
+
| **Naming rule** | 014, 023, 036 |
|
|
35
|
+
| **JavaScript delivery & charts** | 004, 006, 026, 042 |
|
|
35
36
|
| **Time dimensions** | 006, 025 |
|
|
36
|
-
| **Routes, URLs & naming** | 005, 007, 008, 009, 011, 013, 022, 024, 025 |
|
|
37
|
+
| **Routes, URLs & naming** | 005, 007, 008, 009, 011, 013, 022, 024, 025, 040, 041 |
|
|
37
38
|
| **Snapshots & publishing** | 009, 020, 028, 033, 034 |
|
|
38
39
|
| **AI agents & guidance** | 010, 015, 021 |
|
|
39
|
-
| **
|
|
40
|
-
| **
|
|
41
|
-
| **
|
|
40
|
+
| **The doctor & checks** | 021, 025, 032, 033, 035 |
|
|
41
|
+
| **Releases & upgrades** | 015, 021, 032, 034, 035, 036, 037, 042 |
|
|
42
|
+
| **Accessibility & keyboard** | 024, 042 |
|
|
43
|
+
| **Security** | 003, 025, 028, 031, 032, 034, 035 |
|
|
42
44
|
| **Testing** | 003 |
|
|
45
|
+
| **Roadmap & planning** | 001, 037 |
|
|
43
46
|
|
|
44
47
|
## Chronological
|
|
45
48
|
|
|
@@ -79,7 +82,15 @@ them when in doubt: `grep -l "Triggers:.*fork" docs/decisions/*.md`.
|
|
|
79
82
|
| 032 | Janela Will Not Read a Model It Cannot Scope | 2026-09-20 | Accepted |
|
|
80
83
|
| 033 | A Snapshot Is Told Who Owns It | 2026-09-21 | Accepted |
|
|
81
84
|
| 034 | Janela Will Not Freeze a Scope the Host Has Not Named | 2026-09-21 | Accepted |
|
|
85
|
+
| 035 | A Check Does What Janela Does, or It Says What It Saw | 2026-09-22 | Accepted |
|
|
86
|
+
| 036 | Janela Publishes What a Theme May Target, and Vitral Is Only One | 2026-09-22 | Accepted |
|
|
87
|
+
| 037 | 1.0 Means the Surface Stops Moving, Not That Janela Is Finished | 2026-09-23 | Accepted |
|
|
88
|
+
| 038 | A Ratio Is a Measure of Its Own, Stored as a Fraction and Read as a Percentage | 2026-09-24 | Proposed |
|
|
89
|
+
| 039 | A Pane Can Hold Words, and Only Code Writes Markup | 2026-09-24 | Accepted |
|
|
90
|
+
| 040 | A Host Can Fix a Frame's Filter, and No Click Removes It | 2026-09-24 | Accepted |
|
|
91
|
+
| 041 | A Host Finds Its Frame by Owner and Key | 2026-09-24 | Accepted |
|
|
92
|
+
| 042 | A Chart's Title Is a Figcaption, and the Canvas Points to It | 2026-09-28 | Accepted |
|
|
82
93
|
|
|
83
94
|
## Next number
|
|
84
95
|
|
|
85
|
-
Next ADR:
|
|
96
|
+
Next ADR: 043
|
data/docs/multi-tenancy.md
CHANGED
|
@@ -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
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
Topics: roadmap, releases, scope, planning
|
|
3
|
+
---
|
|
4
|
+
|
|
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>
|
|
124
|
+
|
|
125
|
+
Janela is alpha. It works, it is tested against a real Rails application
|
|
126
|
+
in a real browser, and its public surface has changed in three of the
|
|
127
|
+
last four releases. This page says what has to be true before that stops,
|
|
128
|
+
what is in the next release, and what is deliberately not coming. The
|
|
129
|
+
reasoning behind it is ADR 037.
|
|
130
|
+
|
|
131
|
+
## What 1.0 means
|
|
132
|
+
|
|
133
|
+
Not that Janela is finished. ADR 001 commits the project to the
|
|
134
|
+
load-bearing 5% of a BI tool and to staying small enough to fork, so a
|
|
135
|
+
1.0 measured against what a commercial tool ships would never arrive.
|
|
136
|
+
|
|
137
|
+
1.0 is three claims you can check.
|
|
138
|
+
|
|
139
|
+
**The public surface stops moving.** Everything listed as the contract in
|
|
140
|
+
[Theming Janela](theming), the measures and dimensions DSL, `janela_pane`
|
|
141
|
+
and `janela_frame`, the pane URL shape and the dashboard filter
|
|
142
|
+
parameters change only on a major version after 1.0. Until then they can
|
|
143
|
+
change in any release, and every change of that kind carries an entry in
|
|
144
|
+
`UPGRADING.md`.
|
|
145
|
+
|
|
146
|
+
**A pane can be read.** Janela draws tables, bars and lines. A
|
|
147
|
+
part-to-whole split currently has to be drawn as bars, a table row cannot
|
|
148
|
+
carry context beside its label, and a chart takes Chart.js's default
|
|
149
|
+
proportions whether or not they suit the page. Those are not extra
|
|
150
|
+
features. They are the 5% not finished, and most of them were found by
|
|
151
|
+
people installing the gem rather than reading it.
|
|
152
|
+
|
|
153
|
+
**The doctor can be trusted.** `rails janela:doctor` checks an
|
|
154
|
+
installation for the mistakes that produce a dashboard showing numbers
|
|
155
|
+
nobody should see. Two of its checks have no test that makes them fire.
|
|
156
|
+
A check nobody can prove is working is a check nobody should rely on.
|
|
157
|
+
|
|
158
|
+
## In 1.0
|
|
159
|
+
|
|
160
|
+
The eight lights burning in the near ground.
|
|
161
|
+
|
|
162
|
+
| Issue | What |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| [#24](https://github.com/retail-tasker/janela/issues/24) | A chart's height and aspect ratio are the host's to set |
|
|
165
|
+
| [#27](https://github.com/retail-tasker/janela/issues/27) | A ratio measure, so refusing `average:` over a boolean offers somewhere to go |
|
|
166
|
+
| [#30](https://github.com/retail-tasker/janela/issues/30) | Doughnut and pie, and a categorical palette that makes them readable |
|
|
167
|
+
| [#31](https://github.com/retail-tasker/janela/issues/31) | A pane says how prominent it is |
|
|
168
|
+
| [#34](https://github.com/retail-tasker/janela/issues/34) | A table pane carries an attribute column beside its label |
|
|
169
|
+
| [#53](https://github.com/retail-tasker/janela/issues/53) | The last two doctor checks get tests |
|
|
170
|
+
| [#14](https://github.com/retail-tasker/janela/issues/14) | A Sprockets host serves the engine's JavaScript, or is told it cannot |
|
|
171
|
+
| [#6](https://github.com/retail-tasker/janela/issues/6) | How contributions are accepted, who cuts a release, where to report a vulnerability |
|
|
172
|
+
|
|
173
|
+
Progress is tracked on the
|
|
174
|
+
[1.0 milestone](https://github.com/retail-tasker/janela/milestone/2).
|
|
175
|
+
|
|
176
|
+
## Horizonte
|
|
177
|
+
|
|
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.
|
|
180
|
+
|
|
181
|
+
- **Drill-down on time panes** ([#18](https://github.com/retail-tasker/janela/issues/18)).
|
|
182
|
+
Clicking a month could filter every other pane to it, or narrow that
|
|
183
|
+
pane to weeks within it. Both are reasonable, they need different
|
|
184
|
+
things from the URL, and ADR 006 left the question open on purpose. It
|
|
185
|
+
needs a decision record before any code.
|
|
186
|
+
- **A command palette for the demo** ([#41](https://github.com/retail-tasker/janela/issues/41)).
|
|
187
|
+
The demo site, not the gem.
|
|
188
|
+
- **A scroll drift on the gallery page** ([#45](https://github.com/retail-tasker/janela/issues/45)).
|
|
189
|
+
Cosmetic, demo only, and possibly not worth fixing.
|
|
190
|
+
|
|
191
|
+
## Not coming
|
|
192
|
+
|
|
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.
|
|
198
|
+
|
|
199
|
+
Natural-language query. A separate data warehouse. A row-level-security
|
|
200
|
+
subsystem, because your application already has Pundit or CanCanCan and
|
|
201
|
+
Janela reads through it. A refresh-scheduling interface, because you
|
|
202
|
+
already have a scheduler and snapshots are an ActiveJob. An embedding
|
|
203
|
+
SDK. A mobile application. Print and paginated reports. A drag-and-drop
|
|
204
|
+
visual dashboard designer, though frames and panes are database records,
|
|
205
|
+
so an application can build its own editor on top of them.
|
|
206
|
+
|
|
207
|
+
Whether Janela should help arrange panes on a page is genuinely open
|
|
208
|
+
([#29](https://github.com/retail-tasker/janela/issues/29)). The default
|
|
209
|
+
answer is that layout belongs to your application, and changing it would
|
|
210
|
+
need a decision record first.
|
|
211
|
+
|
|
212
|
+
## How this page stays honest
|
|
213
|
+
|
|
214
|
+
It is updated when a release lands, not on a schedule, and it carries no
|
|
215
|
+
dates. A roadmap with dates on a project this size would be wrong within
|
|
216
|
+
a fortnight and would train you to ignore it. If something here has been
|
|
217
|
+
true for a long time and nothing has moved, that is worth reading as the
|
|
218
|
+
signal it is.
|