houseaccount 0.1.0 → 0.3.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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +163 -1
  3. data/MIT-LICENSE +22 -0
  4. data/README.md +230 -20
  5. data/art/autopilot.svg +9 -0
  6. data/art/design.svg +8 -0
  7. data/art/down.html.erb +95 -0
  8. data/art/fountain.svg +9 -0
  9. data/art/goldrush.svg +7 -0
  10. data/art/houseaccount.svg +9 -0
  11. data/art/houston.svg +8 -0
  12. data/art/icons.txt +184 -0
  13. data/config/locales/houseaccount.en.yml +12 -0
  14. data/lib/houseaccount/down.rb +19 -0
  15. data/lib/houseaccount/engine.rb +53 -0
  16. data/lib/houseaccount/form_builder/pins.rb +30 -0
  17. data/lib/houseaccount/form_builder.rb +84 -0
  18. data/lib/houseaccount/helpers/chats/asides.rb +49 -0
  19. data/lib/houseaccount/helpers/chats/asks.rb +40 -0
  20. data/lib/houseaccount/helpers/chats.rb +42 -0
  21. data/lib/houseaccount/helpers/dialogs.rb +47 -0
  22. data/lib/houseaccount/helpers/flows.rb +45 -0
  23. data/lib/houseaccount/helpers/heads.rb +42 -0
  24. data/lib/houseaccount/helpers/logos.rb +43 -0
  25. data/lib/houseaccount/helpers/notices.rb +55 -0
  26. data/lib/houseaccount/helpers.rb +13 -0
  27. data/lib/houseaccount/marks.rb +14 -0
  28. data/lib/houseaccount/message.rb +11 -0
  29. data/lib/houseaccount/version.rb +2 -2
  30. data/lib/houseaccount.rb +35 -2
  31. data/public/_headers +28 -0
  32. data/public/css/houseaccount.css +1 -0
  33. data/public/down/autopilot.html +87 -0
  34. data/public/down/design.html +87 -0
  35. data/public/down/fountain.html +87 -0
  36. data/public/down/goldrush.html +87 -0
  37. data/public/down/houseaccount.html +84 -0
  38. data/public/down/houston.html +87 -0
  39. data/public/favicon/autopilot-180.png +0 -0
  40. data/public/favicon/autopilot-192.png +0 -0
  41. data/public/favicon/autopilot-512.png +0 -0
  42. data/public/favicon/autopilot-96.png +0 -0
  43. data/public/favicon/autopilot-dev.svg +9 -0
  44. data/public/favicon/autopilot.ico +0 -0
  45. data/public/favicon/autopilot.svg +9 -0
  46. data/public/favicon/autopilot.webmanifest +12 -0
  47. data/public/favicon/design-180.png +0 -0
  48. data/public/favicon/design-192.png +0 -0
  49. data/public/favicon/design-512.png +0 -0
  50. data/public/favicon/design-96.png +0 -0
  51. data/public/favicon/design-dev.svg +8 -0
  52. data/public/favicon/design.ico +0 -0
  53. data/public/favicon/design.svg +8 -0
  54. data/public/favicon/design.webmanifest +12 -0
  55. data/public/favicon/fountain-180.png +0 -0
  56. data/public/favicon/fountain-192.png +0 -0
  57. data/public/favicon/fountain-512.png +0 -0
  58. data/public/favicon/fountain-96.png +0 -0
  59. data/public/favicon/fountain-dev.svg +9 -0
  60. data/public/favicon/fountain.ico +0 -0
  61. data/public/favicon/fountain.svg +9 -0
  62. data/public/favicon/fountain.webmanifest +12 -0
  63. data/public/favicon/goldrush-180.png +0 -0
  64. data/public/favicon/goldrush-192.png +0 -0
  65. data/public/favicon/goldrush-512.png +0 -0
  66. data/public/favicon/goldrush-96.png +0 -0
  67. data/public/favicon/goldrush-dev.svg +7 -0
  68. data/public/favicon/goldrush.ico +0 -0
  69. data/public/favicon/goldrush.svg +7 -0
  70. data/public/favicon/goldrush.webmanifest +12 -0
  71. data/public/favicon/houseaccount-180.png +0 -0
  72. data/public/favicon/houseaccount-192.png +0 -0
  73. data/public/favicon/houseaccount-512.png +0 -0
  74. data/public/favicon/houseaccount-96.png +0 -0
  75. data/public/favicon/houseaccount-dev.svg +9 -0
  76. data/public/favicon/houseaccount.ico +0 -0
  77. data/public/favicon/houseaccount.svg +9 -0
  78. data/public/favicon/houseaccount.webmanifest +12 -0
  79. data/public/favicon/houston-180.png +0 -0
  80. data/public/favicon/houston-192.png +0 -0
  81. data/public/favicon/houston-512.png +0 -0
  82. data/public/favicon/houston-96.png +0 -0
  83. data/public/favicon/houston-dev.svg +8 -0
  84. data/public/favicon/houston.ico +0 -0
  85. data/public/favicon/houston.svg +8 -0
  86. data/public/favicon/houston.webmanifest +12 -0
  87. data/public/favicon.ico +0 -0
  88. data/public/font/bootstrap-icons-6LILWPE2.woff2 +0 -0
  89. data/public/font/quicksand-500-latin-QHYSRFVK.woff2 +0 -0
  90. data/public/font/quicksand-500-latin-ext-QZ4PXVHB.woff2 +0 -0
  91. data/public/font/quicksand-500-latin-ext.woff2 +0 -0
  92. data/public/font/quicksand-500-latin.woff2 +0 -0
  93. data/public/font/quicksand-500-vietnamese-RRA5CUQB.woff2 +0 -0
  94. data/public/font/quicksand-500-vietnamese.woff2 +0 -0
  95. data/public/index.html +298 -0
  96. data/public/js/houseaccount.js +144 -0
  97. data/public/logo/autopilot.png +0 -0
  98. data/public/logo/autopilot.svg +9 -0
  99. data/public/logo/design.png +0 -0
  100. data/public/logo/design.svg +8 -0
  101. data/public/logo/fountain.png +0 -0
  102. data/public/logo/fountain.svg +9 -0
  103. data/public/logo/goldrush.png +0 -0
  104. data/public/logo/goldrush.svg +7 -0
  105. data/public/logo/houseaccount.png +0 -0
  106. data/public/logo/houseaccount.svg +9 -0
  107. data/public/logo/houston.png +0 -0
  108. data/public/logo/houston.svg +8 -0
  109. data/public/page/400.html +118 -0
  110. data/public/page/404.html +118 -0
  111. data/public/page/406-unsupported-browser.html +118 -0
  112. data/public/page/422.html +118 -0
  113. data/public/page/500.html +118 -0
  114. data/public/site.webmanifest +12 -0
  115. data/public/theme/bootstrap.css +11 -0
  116. data/public/theme/dawn.css +189 -0
  117. data/public/theme/dracula.css +189 -0
  118. data/public/theme/gruvbox.css +189 -0
  119. data/public/theme/monokai.css +190 -0
  120. data/public/theme/nord.css +189 -0
  121. data/public/theme/one_dark.css +189 -0
  122. data/public/theme/solarized.css +188 -0
  123. data/public/theme/tokyo_night.css +189 -0
  124. data/public/v0.3.0/css/houseaccount.css +1 -0
  125. data/public/v0.3.0/font/bootstrap-icons-6LILWPE2.woff2 +0 -0
  126. data/public/v0.3.0/font/quicksand-500-latin-QHYSRFVK.woff2 +0 -0
  127. data/public/v0.3.0/font/quicksand-500-latin-ext-QZ4PXVHB.woff2 +0 -0
  128. data/public/v0.3.0/font/quicksand-500-latin-ext.woff2 +0 -0
  129. data/public/v0.3.0/font/quicksand-500-latin.woff2 +0 -0
  130. data/public/v0.3.0/font/quicksand-500-vietnamese-RRA5CUQB.woff2 +0 -0
  131. data/public/v0.3.0/font/quicksand-500-vietnamese.woff2 +0 -0
  132. data/public/v0.3.0/js/houseaccount.js +144 -0
  133. data/public/v0.3.0/theme/bootstrap.css +11 -0
  134. data/public/v0.3.0/theme/dawn.css +189 -0
  135. data/public/v0.3.0/theme/dracula.css +189 -0
  136. data/public/v0.3.0/theme/gruvbox.css +189 -0
  137. data/public/v0.3.0/theme/monokai.css +190 -0
  138. data/public/v0.3.0/theme/nord.css +189 -0
  139. data/public/v0.3.0/theme/one_dark.css +189 -0
  140. data/public/v0.3.0/theme/solarized.css +188 -0
  141. data/public/v0.3.0/theme/tokyo_night.css +189 -0
  142. metadata +190 -12
  143. data/LICENSE.txt +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3679e7fcb39801bbbade5f8a0324ea46e5c4c71bc50b498ac139a4b7964abecf
4
- data.tar.gz: ebaa5bbd77e6396ffd4e8cb7e7fbcee89538f6a69a474d0ca80f3bdda38c63d7
3
+ metadata.gz: 939b9379b9f9074c36dfafeaa68f37c91154a36976cd64025170653a71b1918a
4
+ data.tar.gz: 552f0b7b147cb46cb7edab29c293a6d570cd936f6198028694364f8de9f895c0
5
5
  SHA512:
6
- metadata.gz: dccd03d4fe716cc5e28a0d3fa2fe93695e1cc912267179ca0b1c9468950c1d470e0bbcae69244a27aebc380dbd0a9e85f56c83f6dd6f422b4da96d3a1573a319
7
- data.tar.gz: 8f84db9c4f3b25f78bf8c0ff788f46f420f76f4748e817ae839f7e3195d886c1b4e225e1f390c49cbddd4b180d998a30e5d40c8049c9a6a94aff0c66b98a1885
6
+ metadata.gz: 0da5799f9fcfbb25157745e1641dbbace60b62d40eb344774ea123e2e909f08d912384810b1d9500a5c17d16ad7b17323f5a8fdd7e04ed5907d861a4e9adfb82
7
+ data.tar.gz: 06e33a1b81ca80e1ccc595be50bdfcc64b4756cda9e1de9741251e91183e5d742a62ed552c22d4017e1149f40de5196947d63afef0d1ee3026e761315f34396e
data/CHANGELOG.md CHANGED
@@ -7,6 +7,168 @@ For more information about changelogs, check [Keep a Changelog](http://keepachan
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## 0.3.0 - 2026-09-14
11
+
12
+ * [Fix] A chat scrolls inside its card, and the page stands still
13
+
14
+ The flow holding a thread is a flex item of `main`, and `flex: 1` sized it to its content
15
+ over the height it was given, so a long thread grew the page and the way out scrolled off.
16
+ The column is `flex: none` at the window's height now, and only the thread scrolls.
17
+
18
+ * [Fix] Sending a question keeps the reader where they were
19
+
20
+ The ask form under a chat asks Turbo to `replace`, so the redirect back to the page it is
21
+ on is a refresh — morphed in place, scroll kept — rather than a visit landing at the top.
22
+
23
+ * [Fix] A Turbo refresh morphs the page and keeps the scroll
24
+
25
+ The head tags now say `turbo-refresh-method: morph` and `turbo-refresh-scroll: preserve`,
26
+ so an answer landing in a thread leaves the reader at the bottom where they typed, rather
27
+ than at the top of a page drawn again. The layout also names `HouseAccount.app` by its
28
+ current spelling where a page has no title of its own.
29
+
30
+ * [Feature] `flow` draws the page of a signup flow around a card
31
+
32
+ The mark over the wall, a title, a line, one card and the way out, from a host layout's
33
+ one call. The view says its words in `content_for` — `:title`, `:subtitle` and `:width`,
34
+ the card's, which is how a chat's page gets a wider card than a form's — and the layout
35
+ passes `footer:`, pieces joined by a middot, and `logo:`. The column and the card are the
36
+ gem's rules again, `.flow` and `.flow-card`.
37
+
38
+ * [Fix] No gutter is kept for a scrollbar a page has not got
39
+
40
+ Bootstrap 6 reserves the column on every page; a browser with classic scrollbars painted
41
+ its empty track beside a page that fit. The gutter now appears with the scrollbar alone.
42
+
43
+ * [Fix] The versioned copy follows the build until the version is tagged
44
+
45
+ The head helper links `/v<version>/`, and that folder was written once and left, so a
46
+ host reading the gem from a path kept the stylesheet of the day the folder was made.
47
+ Every build now rewrites it until `v<version>` is tagged, and never after.
48
+
49
+ * [Feature] `HouseAccount.one_time_code` is the text that carries a code
50
+
51
+ Every app texting a 6-digit code sends the same two lines, the sentence and the
52
+ `@host #code` Safari reads a code from, and reads them off the gem's locale file rather
53
+ than keeping a copy of its own.
54
+
55
+ * [Breaking change] The constant is `HouseAccount`
56
+
57
+ `HouseAccount::Message`, `HouseAccount::Engine`, `HouseAccount.app`: the house's name
58
+ as the house writes it. The gem and the npm package stay `houseaccount`, since a gem's
59
+ name fixes only the file Bundler requires.
60
+
61
+ * [Feature] A host's layout nests on the gem's
62
+
63
+ A layout hands its page over as `content_for :content` and renders the gem's as a
64
+ template; the bare layout's body now fills the window. The wall stays the gem's, from
65
+ `data-controller='wall'`; what a host draws in front of it, a column and a card say, is
66
+ the host's to style.
67
+
68
+ * [Fix] Body text is Bootstrap's own, and a chat bubble has its sheen back
69
+
70
+ The stylesheet set every page to Helvetica at 14px, a choice made for tables that came
71
+ over with their rules; a page a customer reads on a phone wants Bootstrap's system face at
72
+ 16px, and a host wanting another sets the two properties itself. A bubble wears
73
+ `--bs-gradient` again, as a message thread's do.
74
+
75
+ * [Feature] `dialog` draws a link and the dialog it opens
76
+
77
+ Bootstrap's `<dialog>` with a heading, an X, the block and an `Okay`, and the link that
78
+ opens it through the plugin's own `data-bs-toggle`, sharing an id made from the link's
79
+ words — so no page writes the markup or invents a name, and the `dialog` controller
80
+ carried here from fountain goes.
81
+
82
+ * [Fix] A phone field refuses an eleventh digit rather than taking it and cutting it back
83
+
84
+ A submit shut until the field is valid saw the eleven digits before the format cut them
85
+ to ten, and stayed shut. The key is refused on a full field; a whole number pasted or
86
+ autofilled is reshaped and the field says `input` again; and the country code a number
87
+ arrives with is dropped, since no NANP area code starts with a 1 — which is what
88
+ fountain's `national` controller did on its own.
89
+
90
+ * [Feature] `notices` draws the flash as toasts
91
+
92
+ Every message in the flash as a toast at the bottom right — green for a notice, red for
93
+ an alert, neutral for a key of the host's own — visible at first paint, held while read
94
+ and gone after a moment, the way recourse shows them. The gem's layout calls it, so a
95
+ page it draws shows what a redirect had to say.
96
+
97
+ * [Feature] `HouseAccount::FormBuilder` is the house's form, `pin_field` included
98
+
99
+ Given as `builder:` or a controller's `default_form_builder`: `fieldset` is the one-column
100
+ grid a form's controls sit in, with a legend where given, a label is a `form-label`, a
101
+ text or phone field a large control, the phone one shaped by the `phone` controller as it
102
+ is typed, a submit or a button the solid pill, a `type: :button` one the outlined pill, and a
103
+ disabled one given a `title` says why on hover — `Available soon` — from a wrapper that
104
+ hears the mouse for it. `pin_field` is one real field rendered by Bootstrap
105
+ as six slots, with the `otp` controller from fountain: the slots drawn on a Turbo visit, a pasted or autofilled code sent on its own,
106
+ a refused one kept with the caret at its end.
107
+
108
+ * [Feature] A `<select data-controller='combobox'>` is Bootstrap's combobox, and any `data-turbo-confirm` asks through a dialog
109
+
110
+ The bundle dresses a select — a toggle, a menu with a search box, a check per pick where
111
+ it is `multiple`, an `All` row, a figure beside an option — and keeps the select as what
112
+ the form submits, announcing a pick as its own `change`. Recourse and any app write a
113
+ select and nothing more. The confirm dialog is built by the bundle on the first ask, so
114
+ no page has to carry the markup, and its answer wears the words of the button that asked.
115
+
116
+ * [Feature] Faster first paint: the icon font cut to the glyphs the house draws, Quicksand served from here, and a page's own version linked and kept for a year
117
+
118
+ Bootstrap Icons shipped two thousand glyphs in 134 KB; `art/icons.txt` names the ones a
119
+ HouseAccount page may draw and `rake icons` cuts the font and its stylesheet to those.
120
+ Quicksand comes from `/font` rather than Google, so no page reaches another origin. The
121
+ engine serves this version's copy under `/v<version>/`, cached immutable, and
122
+ `houseaccount_head_tags` links it, so a returning reader fetches nothing.
123
+
124
+ ## 0.2.0 - 2026-09-13
125
+
126
+ * [Feature] The front end of every HouseAccount app, extracted from recourse and the apps
127
+
128
+ One stylesheet and one script — Bootstrap 6 alpha, Bootstrap Icons, Turbo, Stimulus and the
129
+ twenty controllers behind the house's pages — served by the engine at `/css/houseaccount.css`
130
+ and `/js/houseaccount.js`, and published at https://design.houseaccount.com under the same
131
+ paths for a page not on the gem. The nine palettes under `/theme`, every app's mark under
132
+ `/logo` and `/favicon`, and the five error pages under `/page`, which the engine answers a
133
+ failing request with.
134
+
135
+ * [Feature] `houseaccount_head_tags`, `houseaccount_logo`, `chat_with` and `houseaccount_url`
136
+
137
+ A head, a lockup and a thread of bubbles drawn the same way in every app. Which app a host
138
+ is, the gem reads off the Rails application's name, and the head and the down page follow
139
+ it; `houseaccount_logo app: :fountain` draws any of the family's marks seated by its own numbers beside its
140
+ name. `chat_with url:, hints:, messages:` takes messages answering `side`, `text`, `sender`,
141
+ `at`, `delivery` and `cost`, or `HouseAccount::Message`s, and the URL the next one posts to.
142
+
143
+ * [Feature] A layout for a page recourse does not draw
144
+
145
+ `layout 'houseaccount'` on a controller gives a page the shared head — charset, title,
146
+ CSRF and CSP metas, the stylesheet, the script and the icons — then `yield :head`, then
147
+ the page inside `<body>`. `content_for :title` names it; the app's own name stands in.
148
+
149
+ * [Feature] The page an app shows while it is down
150
+
151
+ Its own mark in the middle and the house's at the foot, rendered per app into `/down/<app>.html`
152
+ and answered at `/down.html` by the engine for the app the host named. The design site
153
+ has a mark of its own, a pencil, and wears it.
154
+
155
+ * [Feature] Every release is kept at its own versioned URL
156
+
157
+ `/v0.2.0/css/houseaccount.css` and its script, fonts and palettes stand beside the
158
+ unversioned copies, whole on their own and cached for a year, so a page may pin a
159
+ version and move when it chooses. `rake version` writes a folder once per release.
160
+
161
+ * [Feature] The signup flow's wall of drifting tools, from one attribute
162
+
163
+ `data-controller='wall'` on a page's shell draws the cyanotype of trades and the family's
164
+ marks behind it, from geometry the bundle carries; `.flow-shell`, `.flow-main` and
165
+ `.welcome-card` are the classes the page in front of it wears.
166
+
167
+ * [Feature] A phone is formatted in the browser
168
+
169
+ The server hands over ten digits and `data-controller='phone'`; the controller decides
170
+ `555-555-5555` on a cell and on a field, and gives the field its pattern and placeholder.
171
+
10
172
  ## 0.1.0 - 2026-08-27
11
173
 
12
- * [Feature] First release: a library that loads and does not do anything yet
174
+ * A library that loads and does not do anything yet.
data/MIT-LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2026 Claudio Baccigalupo
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,36 +1,246 @@
1
- # House Account
1
+ # HouseAccount design
2
2
 
3
- The Ruby House Account shares between the apps it runs.
3
+ Everything a HouseAccount page is made of, in one place: the stylesheet and the script every
4
+ page loads, the marks every app is known by, the pages shown when an app is not, and the
5
+ Rails helpers that draw a head, a logo or a chat the same way in every app.
4
6
 
5
- Nothing lives here yet: `require 'houseaccount'` defines the `HouseAccount` namespace and
6
- returns. It is released so that an app can depend on it before there is anything to depend on,
7
- and so the first thing extracted into it arrives as `0.2.0` rather than as a new gem.
7
+ Published three ways from this repo:
8
+
9
+ | Surface | Where | What it is for |
10
+ | --- | --- | --- |
11
+ | Site | https://design.houseaccount.com (staging: https://design.houseparty.dev) | A stable URL for every asset, and a showcase of all of them |
12
+ | Gem | `houseaccount` on RubyGems | A Rails engine: serves the same tree from the gem, and the helpers |
13
+ | npm | `houseaccount` | The controllers and stylesheets as importable source |
8
14
 
9
15
  ## How to install
10
16
 
11
- To install on your system, run
17
+ ```bash
18
+ gem install houseaccount
19
+ ```
20
+
21
+ ```ruby
22
+ # Gemfile
23
+ gem 'houseaccount', '~> 0.3.0'
24
+ ```
25
+
26
+ `~> 0.3.0` stops short of `0.4`, since below 1.0 a minor release may break whatever it likes.
27
+ Rails 8.1 and Ruby 3.2 are the minimum.
28
+
29
+ A page takes the gem's layout, and wears the same head:
30
+
31
+ ```ruby
32
+ class Unauthenticated::AgentsController < ApplicationController
33
+ layout 'houseaccount'
34
+ end
35
+ ```
36
+
37
+ ```erb
38
+ <% content_for :title, 'Authenticating as admin' %>
39
+ <% content_for :head, tag.meta(name: 'robots', content: 'noindex') %>
40
+ <h1>Authenticating as admin</h1>
41
+ ```
42
+
43
+ <p>
44
+ <img src='screenshot/layout-light.png' width='49%' alt='The page in light mode'>
45
+ <img src='screenshot/layout-dark.png' width='49%' alt='The page in dark mode'>
46
+ </p>
47
+
48
+ The layout is the charset, the title, the CSRF and CSP metas, `houseaccount_head_tags`,
49
+ `yield :head`, then `yield` inside a `main.container-fluid` in the `<body>`, and `notices`
50
+ after it, and nothing else. A host wanting more copies
51
+ it into its own `app/views/layouts/`, which wins on being earlier in the view paths.
52
+
53
+ Which of the family an app is, the gem reads off the Rails application's own name: the
54
+ `Goldrush` of `Goldrush::Application`. That is what its tab wears, what `houseaccount_head_tags`
55
+ links without an argument, and whose page the engine answers `/down.html` with — the app's
56
+ mark in the middle and the house's at the foot. An app the family has no mark for wears the
57
+ cube. The same page stands at `https://design.houseaccount.com/down/fountain` for Heroku's
58
+ `MAINTENANCE_PAGE_URL`, which has to be somewhere the app is not — without the `.html`,
59
+ which Pages redirects away from.
60
+
61
+ With the gem in the bundle an app serves `/css/houseaccount.css`, `/js/houseaccount.js`,
62
+ `/theme/*`, `/font/*`, `/logo/*` and `/favicon/*` itself — and its own version's copy under
63
+ `/v0.3.0/`, which is what the head helper links, cached for a year — answers a 400, 404, 406, 422 or 500
64
+ with the house's own page, and has these helpers in every view:
65
+
66
+ ```erb
67
+ <%# In the <head>: the viewport, color-scheme and Turbo refresh metas, the stylesheet, the script,
68
+ and the icons a tab, a phone and a manifest ask for — this app's, by its name. %>
69
+ <%= houseaccount_head_tags %>
70
+
71
+ <%# A mark beside its name, as one word and one link. The house's cube and `houseaccount`
72
+ leading to houseaccount.com by default, the way a page signs itself in a footer; any
73
+ app's own with its name and wherever `href` says. Other options are attributes of the
74
+ link. Under the cursor the whole logo turns the mark's blue. %>
75
+ <%= houseaccount_logo %>
76
+ <%= houseaccount_logo app: :fountain, href: root_path, class: 'fs-3' %>
77
+
78
+ <%# A thread of messages, newest at the foot, and — where `url` is given — the field that
79
+ posts the next one there, suggesting `hints` one at a time in its placeholder. %>
80
+ <%= chat_with url: chat_question_path, hints: Provider::Chat::HINTS, messages: messages %>
81
+
82
+ <%# What the flash says, as toasts at the bottom right: green for a notice, red for an alert,
83
+ neutral for any other key; visible at first paint, gone after a moment or a click, held
84
+ while read. A value in the flash that is not words is left alone. %>
85
+ <%= notices %>
86
+
87
+ <%# A link and the dialog it opens: headed, with an X, holding the block, and closed by an
88
+ `Okay` too (`okay:` words it otherwise, `okay: false` leaves it out). The two share an id
89
+ made from the link's words; any other option is an attribute of the link. %>
90
+ <%= dialog 'I’m not on Google Business', title: 'Not a problem' do %>
91
+ We will look your business up ourselves and match it by hand.
92
+ <% end %>
93
+
94
+ <%# The house's form, from `builder: HouseAccount::FormBuilder` or a controller's
95
+ `default_form_builder`: `fieldset` a one-column grid with a legend where given, a label a
96
+ form-label, a text or phone field a large control — the phone one shaped by the `phone`
97
+ controller — a submit or a button the solid pill, a `type: :button` one the outlined pill, a
98
+ disabled one given a `title` saying why on hover, and
99
+ `pin_field` the six slots a code is typed into, one real field offered by the phone from the
100
+ text that brought it. %>
101
+ <%= form.fieldset do %>
102
+ <%= form.label :pin_confirmation, '6-digit code' %>
103
+ <%= form.pin_field :pin_confirmation, required: true %>
104
+ <%= form.submit 'Confirm' %>
105
+ <% end %>
106
+
107
+ <%# An asset on the design site, for a page this app does not serve — a mail, say. %>
108
+ <%= image_tag houseaccount_url('logo/fountain.png') %>
109
+ ```
110
+
111
+ Two more behaviors need no helper either, only an attribute. A `<select>` becomes Bootstrap's
112
+ combobox — a toggle reading the pick, a menu with a search box, a check per pick where it
113
+ is `multiple` — and stays the one thing the form submits:
12
114
 
13
- gem install houseaccount
115
+ ```erb
116
+ <%= f.select :team_id, Team.pluck(:name, :id), { include_blank: 'None' },
117
+ data: { controller: 'combobox', combobox_placeholder_value: 'Select…',
118
+ search: 'Search…', clear: 'Clear search', no_results: 'No results found' } %>
119
+ ```
14
120
 
15
- To use inside a bundled Ruby project, add this line to the `Gemfile`:
121
+ On a `multiple` select, `data-combobox-all-value` names an `All` row that puts held-back
122
+ options on the menu and `data-combobox-more-value` words the toggle when several are picked
123
+ (`%{first} + %{count} more`); on an option, `data-count` puts a figure beside it and
124
+ `data-hidden` holds it back until `All`. A pick is announced as the select's own `change`.
16
125
 
17
- gem 'houseaccount', '~> 0.1.0'
126
+ And any link or form with `data-turbo-confirm` asks through a Bootstrap dialog the bundle
127
+ draws on the first ask: the first line of the message is the question, the rest are
128
+ paragraphs, and the answer wears the words of the button that asked. A host in another
129
+ language names the other word in `<meta name='houseaccount-cancel'>`.
18
130
 
19
- Below 1.0 the pin names the patch as well as the minor, so `bundle update` stops short of
20
- `0.2.0`. Semantic Versioning lets a `0.x` release break whatever it likes, and only promises
21
- otherwise once the major is real -- at which point the pin loosens to `~> 1.0`.
131
+ `flow` draws the page of a signup flow around a card: the mark over the wall, a title, a
132
+ line under it, the card, and the way out at the foot. A layout of the app's own calls it and
133
+ nests on the gem's layout, giving the page as `content_for :content` and rendering the gem's
134
+ as a template:
135
+
136
+ ```erb
137
+ <% content_for :content do %>
138
+ <%= flow logo: root_path, footer: [button_to('Sign out', session_path, method: :delete, class: 'btn btn-link')] do %>
139
+ <%= yield %>
140
+ <% end %>
141
+ <% end %>
142
+
143
+ <%= render template: 'layouts/houseaccount' %>
144
+ ```
145
+
146
+ The words are the view's, said in `content_for`: `:title` is the `h1`, `:subtitle` the line
147
+ under it, drawn only where there is one, and `:width` how wide the card is, a CSS length that
148
+ is `30rem` unless said — a chat's page says `75rem`. What the layout knows goes in the call:
149
+ `footer:` is the pieces of the foot, joined by a middot, none drawing no foot, and `logo:`
150
+ where the mark links, the house's front door unless said.
151
+
152
+ The wall alone is one attribute, `data-controller='wall'`, on any element that should
153
+ stand on it.
154
+
155
+ A message is anything answering six methods, and `HouseAccount::Message` is one for a host
156
+ that has the facts and no model to hang them on:
157
+
158
+ | Method | What it says |
159
+ | --- | --- |
160
+ | `side` | `:left` for the other party's bubble, `:right` for the reader's |
161
+ | `text` | what was said — a string, or markup the host rendered; nil draws the typing dots |
162
+ | `sender` | who said it, shown small above the bubble; nil shows nobody |
163
+ | `at` | when, as `3 minutes ago` with the moment itself in a tooltip; nil shows no time |
164
+ | `delivery` | `:delivered`, `:sent`, `:failed` or `:undelivered`, a ✓ or ✕ in its tone; nil for none |
165
+ | `cost` | dollars the message cost, shown as `· $0.0123`; nil for none |
166
+
167
+ ```ruby
168
+ HouseAccount::Message.new side: :left, text: answer.note, sender: 'Claude', at: answer.created_at,
169
+ cost: Chat.spent_on(answer.input_usage, answer.output_usage)
170
+ ```
171
+
172
+ A code texted to a phone is the same words from every app, `HouseAccount.one_time_code`:
173
+ the sentence a person reads, and under it the `@host #code` line that offers the code above
174
+ the keyboard on the phone it arrived on. The host is the application's default one unless
175
+ `host:` says otherwise, and the copy is the gem's `config/locales` entry.
176
+
177
+ ```ruby
178
+ HouseAccount.one_time_code contact.pin
179
+ # => "Enter this 6-digit code to proceed: 482913\n\n@houseaccount.com #482913"
180
+ ```
181
+
182
+ A page not on the gem links the two files from the site, and nothing else:
183
+
184
+ ```html
185
+ <link rel='stylesheet' href='https://design.houseaccount.com/css/houseaccount.css'>
186
+ <script type='module' src='https://design.houseaccount.com/js/houseaccount.js'></script>
187
+ ```
188
+
189
+ Those follow the latest release. Every release is also kept whole under its own version,
190
+ forever and cached for a year, for a page that would rather move on its own terms:
191
+
192
+ ```html
193
+ <link rel='stylesheet' href='https://design.houseaccount.com/v0.3.0/css/houseaccount.css'>
194
+ <script type='module' src='https://design.houseaccount.com/v0.3.0/js/houseaccount.js'></script>
195
+ ```
196
+
197
+ A host with a Content Security Policy allows that origin for `style-src`, `script-src` and
198
+ `font-src`.
199
+
200
+ ## What is in the bundle
201
+
202
+ `css/houseaccount.css` is Bootstrap 6 alpha as upstream ships it, Bootstrap Icons cut down to
203
+ the glyphs `art/icons.txt` names — the ones `unicon` maps and the few written out by name —
204
+ and then the house's rules by concern under `app/stylesheets/houseaccount/`. Quicksand, the
205
+ lockup's face, is served from `/font` too; nothing on a page reaches another origin. `js/houseaccount.js` is
206
+ Turbo (with the cable element `turbo-rails` signs its streams for), Bootstrap, and a Stimulus
207
+ application with every controller the house's markup names, started once under
208
+ `window.Stimulus`. Everything is drawn for light and dark alike: every color is a Bootstrap
209
+ token or a `light-dark()` pair, and `data-bs-theme` on `<html>` forces either. A phone is
210
+ formatted in the browser: the server hands over ten digits and
211
+ `data-controller='phone'`, and the controller decides `555-555-5555`.
22
212
 
23
213
  ## Development
24
214
 
25
- `bin/setup` gets a clone working, `bin/console` opens a prompt with the library loaded, and
26
- `bundle exec rake` runs the suite and the linter -- which is what CI runs too.
215
+ ```bash
216
+ bin/setup # bundle and npm install
217
+ bundle exec rake # build, test at 100% coverage, RuboCop, the file-length ceiling
218
+ ./build-icons.sh # after editing a drawing under art/
219
+ rake icons # after editing art/icons.txt, the glyphs the icon font keeps
220
+ cd test/dummy && bin/rails server
221
+ ```
222
+
223
+ The built `public/css`, `public/js`, `public/font` and `public/theme` are committed: the gem
224
+ serves them, and `spec.files` is `git ls-files`. `rake assets` rebuilds and fails until the
225
+ result is committed, so a source edit cannot ship without its build.
226
+
227
+ While the gem is read from a path, an app in development serves every file `no-cache`, so a
228
+ rebuild shows on the next reload; only a production app keeps the versioned copy for a year.
27
229
 
28
- ## Reference
230
+ Every build also writes `public/v<version>/`, whole, with every URL inside pointing under
231
+ that prefix, until the tag `v<version>` exists; from then on the folder is never written
232
+ again, and a page that pinned it is owed the same bytes for good.
29
233
 
30
- The API reference is built from what RubyGems holds, at
31
- [rubydoc.info/gems/houseaccount](https://rubydoc.info/gems/houseaccount). The source is at
32
- [github.com/HouseAccountEng/houseaccount](https://github.com/HouseAccountEng/houseaccount).
234
+ A release, in order: `CHANGELOG.md`, the version in `lib/houseaccount/version.rb` and
235
+ `package.json`, `bundle exec rake` so the versioned copy is the release's, then commit, tag
236
+ `v<version>`, `gem build` and `gem push`, and fast-forward `production` to `main` so the
237
+ site carries both the latest and the versioned copy.
33
238
 
34
- ## License
239
+ ## Cloudflare
35
240
 
36
- MIT, see [LICENSE.txt](LICENSE.txt).
241
+ The site is a Cloudflare Pages project named `design` on this repo: production branch
242
+ `production`, no build command, output directory `public`, custom domain
243
+ `design.houseaccount.com`. `main` deploys as the branch alias `main.design.pages.dev`, which
244
+ `design.houseparty.dev` is a CNAME to. `public/_headers` adds the CORS header a cross-origin
245
+ module script and font need, and marks the hashed fonts immutable; everything else keeps
246
+ Pages' default of revalidating on every load, so a URL never has to change between versions.
data/art/autopilot.svg ADDED
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g fill="none" stroke="#3b8ae8" stroke-width="28" stroke-linecap="round" stroke-linejoin="round">
3
+ <circle cx="256" cy="256" r="190"/>
4
+ <path d="M66 256 L206 256"/>
5
+ <path d="M306 256 L446 256"/>
6
+ <path d="M256 306 L256 446"/>
7
+ <circle cx="256" cy="256" r="50"/>
8
+ </g>
9
+ </svg>
data/art/design.svg ADDED
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g fill="none" stroke="#3b8ae8" stroke-width="28" stroke-linecap="round" stroke-linejoin="round" transform="rotate(45 256 256)">
3
+ <path d="M196 60 L316 60 L316 372 L196 372 Z"/>
4
+ <path d="M196 120 L316 120"/>
5
+ <path d="M196 372 L256 470 L316 372"/>
6
+ <path d="M232 431 L256 470 L280 431 Z" fill="#1B5288"/>
7
+ </g>
8
+ </svg>
data/art/down.html.erb ADDED
@@ -0,0 +1,95 @@
1
+ <%# The page an app shows while it is down: its own mark and name in the middle, the house's
2
+ at the foot — or the house's alone, for an app not in the family — and nothing that
3
+ needs the app to be up — Quicksand from Google, the icons
4
+ from the design site, the two blues written in. Rendered per app by `rake pages` into
5
+ public/down/<app>.html, and answered at /down.html by the engine once the host has named
6
+ itself in `Houseaccount.app`. -%>
7
+ <!doctype html>
8
+ <html lang='en'>
9
+ <head>
10
+ <meta charset='utf-8'>
11
+ <meta name='viewport' content='width=device-width, initial-scale=1'>
12
+ <title><%= app %></title>
13
+ <meta name='color-scheme' content='light dark'>
14
+ <link rel='icon' href='https://design.houseaccount.com/favicon/<%= app %>.svg' type='image/svg+xml'>
15
+ <link rel='icon' href='https://design.houseaccount.com/favicon/<%= app %>-96.png' type='image/png' sizes='96x96'>
16
+ <link rel='apple-touch-icon' href='https://design.houseaccount.com/favicon/<%= app %>-180.png'>
17
+ <style>
18
+ @font-face {
19
+ font-family: 'Quicksand'; font-style: normal; font-weight: 500; font-display: swap;
20
+ src: url('https://design.houseaccount.com/font/quicksand-500-vietnamese.woff2') format('woff2');
21
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
22
+ }
23
+ @font-face {
24
+ font-family: 'Quicksand'; font-style: normal; font-weight: 500; font-display: swap;
25
+ src: url('https://design.houseaccount.com/font/quicksand-500-latin-ext.woff2') format('woff2');
26
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
27
+ }
28
+ @font-face {
29
+ font-family: 'Quicksand'; font-style: normal; font-weight: 500; font-display: swap;
30
+ src: url('https://design.houseaccount.com/font/quicksand-500-latin.woff2') format('woff2');
31
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
32
+ }
33
+ :root {
34
+ --bg: #fbfcfe;
35
+ --ink: #1B5288;
36
+ --mark: #3b8ae8;
37
+ }
38
+ @media (prefers-color-scheme: dark) {
39
+ :root {
40
+ --bg: #0f1622;
41
+ --ink: #9cc0ec;
42
+ }
43
+ }
44
+ * { box-sizing: border-box; }
45
+ body {
46
+ margin: 0;
47
+ min-height: 100svh;
48
+ display: flex;
49
+ flex-direction: column;
50
+ background: var(--bg);
51
+ font-family: 'Quicksand', 'Trebuchet MS', sans-serif;
52
+ font-weight: 500;
53
+ letter-spacing: 0.01em;
54
+ }
55
+ main {
56
+ flex: 1;
57
+ display: grid;
58
+ place-items: center;
59
+ }
60
+ .lockup {
61
+ white-space: nowrap;
62
+ color: var(--ink);
63
+ line-height: 1;
64
+ }
65
+ /* The mark on the text baseline, sized and nudged by its own numbers, in ems so the
66
+ same pair reads right at both sizes on this page. */
67
+ .lockup svg {
68
+ position: relative;
69
+ vertical-align: baseline;
70
+ margin-right: 0.29em;
71
+ }
72
+ main .lockup { font-size: 48px; }
73
+ footer {
74
+ position: sticky;
75
+ bottom: 0;
76
+ background: var(--bg);
77
+ display: flex;
78
+ justify-content: center;
79
+ padding: 20px 0 26px;
80
+ }
81
+ footer .lockup { font-size: 20px; }
82
+ footer .house { color: var(--mark); }
83
+ </style>
84
+ </head>
85
+ <body>
86
+ <main>
87
+ <p class='lockup'><%= mark[app] %><%= app == :houseaccount ? "<span class='house'>house</span>account" : app %></p>
88
+ </main>
89
+ <%- unless app == :houseaccount -%>
90
+ <footer>
91
+ <p class='lockup'><%= mark[:houseaccount] %><span class='house'>house</span>account</p>
92
+ </footer>
93
+ <%- end -%>
94
+ </body>
95
+ </html>
data/art/fountain.svg ADDED
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g fill="none" stroke="#3b8ae8" stroke-width="28" stroke-linecap="round">
3
+ <path d="M256 472V40"/>
4
+ <path d="M256 472C234 310 180 202 121 143"/>
5
+ <path d="M256 472C278 310 332 202 391 143"/>
6
+ <path d="M256 472C216 348 124 283 51 251"/>
7
+ <path d="M256 472C296 348 388 283 461 251"/>
8
+ </g>
9
+ </svg>
data/art/goldrush.svg ADDED
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g fill="none" stroke="#3b8ae8" stroke-width="28" stroke-linecap="round" stroke-linejoin="round">
3
+ <path d="M152 248 L190 128 L322 128 L360 248 Z"/>
4
+ <path d="M40 384 L78 264 L210 264 L248 384 Z"/>
5
+ <path d="M264 384 L302 264 L434 264 L472 384 Z"/>
6
+ </g>
7
+ </svg>
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g fill="none" stroke="#3b8ae8" stroke-width="28" stroke-linecap="round" stroke-linejoin="round">
3
+ <path d="M256 302Q256 256 295.8 233L406.2 169.3Q446 146.3 446 192.3V319.7Q446 365.7 406.2 388.7L295.8 452.4Q256 475.4 256 429.4Z" fill="#1B5288"/>
4
+ <path d="M216.2 59.6Q256 36.6 295.8 59.6L406.2 123.3Q446 146.3 446 192.3V319.7Q446 365.7 406.2 388.7L295.8 452.4Q256 475.4 216.2 452.4L105.8 388.7Q66 365.7 66 319.7V192.3Q66 146.3 105.8 123.3Z"/>
5
+ <path d="M256 256 95.4 163.3"/>
6
+ <path d="M256 256 416.6 163.3"/>
7
+ <path d="M256 256V441.4"/>
8
+ </g>
9
+ </svg>
data/art/houston.svg ADDED
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g fill="none" stroke="#3b8ae8" stroke-width="28" stroke-linecap="round">
3
+ <circle cx="256" cy="256" r="224"/>
4
+ <path d="M32 256h448"/>
5
+ <path d="M256 32c142 63 142 385 0 448"/>
6
+ <path d="M256 32c-142 63-142 385 0 448"/>
7
+ </g>
8
+ </svg>