HouseAccount design
What makes a page look like HouseAccount's: the marks every app is known by, the lockup they sit in, the wall a signup flow stands on, the palettes, the pages an app shows when it is down or lost, and the one stylesheet and one script that carry all of it.
The Bootstrap 6 underneath is bh, which this gem depends on
and draws on top of rather than bundling: a page links bh's stylesheet and script first and this
one's after, so the house rules win and Bootstrap ships once. The components are documented
there, not here — the form builder, the combobox, the 6-digit code, the phone field, the
dialog, the toasts, the thread of messages and the twenty-two Stimulus controllers are Bootstrap
with the markup written once, and are worth the same to any Rails app. This gem is only what is
the house's.
Published three ways from this repo:
| Surface | Where | What it is for |
|---|---|---|
| Site | https://design.houseaccount.com (staging: https://design.houseparty.dev) | A stable URL for every asset, and a showcase of them |
| Gem | houseaccount on RubyGems |
A Rails engine: serves the same tree from the gem, and the helpers |
| npm | houseaccount |
The same built stylesheet and script, for any CDN that carries it |
How to install
From RubyGems
gem install houseaccount
# Gemfile
gem 'houseaccount', '~> 0.15.0'
bh comes with it; nothing else has to be named.
~> 0.15.0 stops short of 0.16, since below 1.0 a minor release may break whatever it likes.
Rails 8.1 and Ruby 3.2 are the minimum.
A page takes the gem's layout, and wears the same head:
class Unauthenticated::AgentsController < ApplicationController
layout 'houseaccount'
end
<% content_for :title, 'Authenticating as admin' %>
<% content_for :head, tag.meta(name: 'robots', content: 'noindex') %>
<h1>Authenticating as admin</h1>
The layout is the charset, the title, the CSRF and CSP metas, houseaccount_head_tags,
yield :head, then yield inside a main.container-fluid in the <body>, and notices
after it, and nothing else. A host wanting more copies
it into its own app/views/layouts/, which wins on being earlier in the view paths.
Which of the family an app is, the gem reads off the Rails application's own name: the
Goldrush of Goldrush::Application. That is what its tab wears, what houseaccount_head_tags
links without an argument, and whose page the engine answers /down.html with — the app's
mark in the middle and the house's at the foot. An app the family has no mark for wears the
cube. The same page stands at https://design.houseaccount.com/down/fountain for Heroku's
MAINTENANCE_PAGE_URL, which has to be somewhere the app is not — without the .html,
which Pages redirects away from.
With the gem in the bundle an app serves /css/houseaccount.css, /js/houseaccount.js,
/theme/*, /font/*, /logo/* and /favicon/* itself — and its own version's copy under
/v0.15.0/, which is what the head helper links, cached for a year — answers a 400, 404, 406,
422 or 500 with the house's own page, and answers /down.html with this app's.
Which app this is
Read off the Rails application's own name, the Goldrush of Goldrush::Application. That is
what its tab wears, what houseaccount_head_tags links without an argument, and whose page the
engine answers /down.html with — the app's mark in the middle and the house's at the foot. An
app the family has no mark for wears the cube.
The helpers
<%# In the <head>: the viewport, color-scheme and Turbo refresh metas, the stylesheet, the
script, and the icons a tab, a phone and a manifest ask for — this app's, by its name. %>
<%= houseaccount_head_tags %>
<%# The icons alone, for a head of the host's own: this app's unless another of the family is
named, the way a page for customers wears the house's cube rather than the app's. A host of
recourse needs no call at all — the gem answers recourse's `recourses/head` with them. %>
<%= houseaccount_icons :houseaccount %>
<%# A mark beside its name, as one word and one link. Each drawing fills its 512 grid
differently, so equal sizes never align: every mark is rendered at a box of its own and
pushed down by its own nudge, and its ink then stands on the baseline. %>
<%= houseaccount_logo %>
<%= houseaccount_logo app: :fountain, href: root_path, class: 'fs-3' %>
<%# An asset on the design site, for a page this app does not serve — a mail, say. %>
<%= image_tag houseaccount_url('logo/fountain.png') %>
The layout
A page takes the gem's layout and wears the same head:
class Unauthenticated::AgentsController < ApplicationController
layout 'houseaccount'
end
<% content_for :title, 'Authenticating as admin' %>
<% content_for :head, tag.meta(name: 'robots', content: 'noindex') %>
<h1>Authenticating as admin</h1>
It is the charset, the title, the CSRF and CSP metas, houseaccount_head_tags, yield :head,
then yield inside a main.container-fluid, and notices after it — and nothing else. A host
wanting more copies it into its own app/views/layouts/, which wins on being earlier in the
view paths.
The flow, and the wall it stands on
flow is the page a signup flow lives on: the lockup over the cyanotype, a title, a line, one
card, and the way out under it. A layout of the app's own calls it and nests on the gem's:
<% content_for :logo, 'houseaccount' %>
<% content_for :content do %>
<%= flow href: root_path, links: [button_to('Sign out', session_path, method: :delete, class: 'btn btn-link')] do %>
<%= yield %>
<% end %>
<% end %>
<%= render template: 'layouts/houseaccount' %>
href: is where the mark links, the house's front door unless said; links: the pieces of the
foot, joined by a middot, none drawing no foot. The mark and the wall are not asked for, being
what a house flow is. What the page says about itself — content_for :title, :subtitle and
:width — it says in its own view, and content_for :logo is which of the family's marks the
tab wears, for a page a customer reads rather than the app's own people.
The wall alone is one attribute, data-controller='wall', on any element that should stand on
it: fourteen trade tools and the family's five marks drifting up in seven columns, on blueprint
blues of its own so it reads the same whatever the primary is. Under prefers-reduced-motion
the columns stand still.
A code sent to a phone
The same words from every app, in each voice they are said in. written is the sentence a
person reads and, under it, the @host #code line that offers the code above the keyboard on
the phone it arrived on; spoken is the sentence alone, its digits one at a time so a listener
can write them down.
HouseAccount.one_time_code(contact.pin).written
# => "Enter this 6-digit code to proceed: 482913\n\[email protected] #482913"
HouseAccount.one_time_code(contact.pin).spoken
# => "Enter this 6-digit code to proceed: 4, 8, 2, 9, 1, 3"
Off the gem
A page not on the gem links the two files from the site, and nothing else:
<link rel='stylesheet' href='https://design.houseaccount.com/css/houseaccount.css'>
<script type='module' src='https://design.houseaccount.com/js/houseaccount.js'></script>
Those follow the latest release. Every release is also kept whole under its own version, forever and cached for a year, for a page that would rather move on its own terms:
<link rel='stylesheet' href='https://design.houseaccount.com/v0.19.1/css/houseaccount.css'>
<script type='module' src='https://design.houseaccount.com/v0.19.1/js/houseaccount.js'></script>
bh's layer goes under the house's, from the same place, since the house is drawn on top of it and neither stands alone:
<link rel='stylesheet' href='https://design.houseaccount.com/v0.19.1/bh/css/bh.css'>
<link rel='stylesheet' href='https://design.houseaccount.com/v0.19.1/css/houseaccount.css'>
The palettes are beside it, at bh/theme/nord.css and the rest — bh's nine, copied out of the
gem at build time. A version under v is never written again, so that URL is owed its bytes.
A host with a Content Security Policy allows whichever origin it names for style-src,
script-src and font-src.
What is in the bundle
css/houseaccount.css is the house's layer alone: the rules this gem writes over bh's,
followed by Bootstrap Icons cut down to the glyphs art/icons.txt names (the ones unicon
maps and the few written out by name) and the house's three — the page, the lockup and the
wall. Quicksand, the lockup's face, is served from /font too; nothing on a page reaches
another origin. js/houseaccount.js is the house's script alone: wall, the one controller
that is its own, registered with the Stimulus application bh's script starts. bh's two files
are copied beside these at bh/, and a page links bh's first. Everything is drawn for light and dark alike: every color is a Bootstrap
token or a light-dark() pair, and data-bs-theme on <html> forces either. A phone is
formatted in the browser: the server hands over ten digits and
data-controller='phone', and the controller decides 555-555-5555.
Development
bin/setup # bundle and npm install
bundle exec rake # build, test at 100% coverage, RuboCop, the file-length ceiling
./build-icons.sh # after editing a drawing under art/
rake icons # after editing art/icons.txt, the glyphs the icon font keeps
cd test/dummy && bin/rails server
The build reads bh's sources out of the gem — bundle info bh --path — rather than out of
node_modules, so the Gemfile is the one place bh's version is pinned. An edit to bh shows in
this bundle on the next rake assets.
The built public/css, public/js, public/font and public/theme are committed: the gem
serves them, and spec.files is git ls-files. rake assets rebuilds and fails until the
result is committed, so a source edit cannot ship without its build.
While the gem is read from a path, an app in development serves every file no-cache, so a
rebuild shows on the next reload; only a production app keeps the versioned copy for a year.
Every build also writes public/v<version>/, whole, with every URL inside pointing under
that prefix, until the tag v<version> exists; from then on the folder is never written
again, and a page that pinned it is owed the same bytes for good.
A release, in order: CHANGELOG.md, the version in lib/houseaccount/version.rb and
package.json, bundle exec rake so the versioned copy is the release's, then commit, tag
v<version>, gem build and gem push, and fast-forward production to main so the
site carries both the latest and the versioned copy.
Cloudflare
The site is a Cloudflare Pages project named design on this repo: production branch
production, no build command, output directory public, custom domain
design.houseaccount.com. main deploys as the branch alias main.design.pages.dev, which
design.houseparty.dev is a CNAME to. public/_headers adds the CORS header a cross-origin
module script and font need, and marks the hashed fonts immutable; everything else keeps
Pages' default of revalidating on every load, so a URL never has to change between versions.