Skip to content

Releases: assemblycom/website

A blog post gets a rail on each side, and the phone's contents bar stops opening

Choose a tag to compare

@nastyaeremina nastyaeremina released this 26 Aug 18:41
b53dcab

A blog post reads with its contents on one side and its offer on the other. On a wide screen the two rails split: the contents list on the left, the writer's card on the right, in the margin the article was leaving empty. Each pins on its own, so the list no longer scrolls inside whatever the card leaves over, and the card stays on screen for the length of a long read instead of disappearing within the first screenful. Narrower desktops keep the single left rail.

The reading measure grows with it, from 40.5rem to 43rem, which is what the widest comparison table needs — at the old width it was clipped and carrying a scroll shadow.

The phone's contents bar is a label, not a menu. It named the section you were reading and also opened the whole list on tap. The list is the desktop rail's job; on a phone the line only needs to say where you are. The chevron, the panel and everything behind them are gone.

Smaller corrections

  • The hero's typing caret sat below the baseline of the line it was typing on. It was a "|" glyph carrying the font's own descender; it is drawn as a rule now.
  • The help desk widget's rows drop their hairline on the hero in light, where the fill was already a legible step off the card.
  • The client intake widget's dot field shimmers on hover, the light masked by the dots so the paper between them stays put.
  • Customer story rows are square. A radius on a row whose only edge is its top rule bent that rule into corners with no sides to meet.

Separately, and not part of this build: every sidebar CTA on the blog was rewritten to fit two lines in the card, and the comparison table headers were shortened so none of them wraps to three. Those live in Ghost and are already out.

Pasted blog tables get a header, and two competitor marks are current

Choose a tag to compare

@nastyaeremina nastyaeremina released this 25 Aug 20:01
66470fe

Pasted blog tables get a real header. A table pasted out of Google Docs
arrived with no header at all: every cell was a body cell, so none of the header
styling the blog's tables are designed around could reach the row naming the
columns. A reader got a "Pros / Cons" line set as ordinary copy, no fill and no
rule, sitting above rows that looked exactly like it. The same post's
hand-authored tables got the band and the alignment, so two tables that are the
same component read as different ones.

The first row of such a table is now promoted to a real header, and the paste's
own centring and bold go with it. Those two were written inline on elements
inside each cell, where they outrank a stylesheet aimed at the cell itself, which
is why no amount of CSS was ever going to reach them. 32 tables across 9 posts,
and anything pasted from now on.

Only a table carrying no header of its own is touched, so a hand-authored one is
never reinterpreted.

Review screenshots keep the corners they were captured with. A G2 or Capterra
review is a capture of a card that already has its own rounded corner, flush to
the edge of the image. The blog rounded every image again at a wider curve, which
cut across that corner and shaved the card's outline either side of it. The card
looked trimmed rather than rounded. The rule beside it already dropped the border
on these images for exactly that reason, the card being its own edge; the radius
was the same case and had been missed.

Product screenshots keep the rounding and the outline. Those are white to the
edge with no frame of their own, which is what the treatment is for.

Dubsado's and SmartVault's marks are current. Dubsado's was a 50 by 50 crop
of its app icon, drawn at 28px and cropped past the icon's rounded corners, so it
filled its frame as a blurry grey square instead of reading as a logo.
SmartVault had rebranded since these were captured: the green key on a document
is retired in favour of a mulberry vault door, so that one was stale rather than
merely small. Both now come from each company's own current artwork.

The comparison cards take the customer stories' card surface. They were a
borderless muted fill; they now use the same lighter surface and hairline outline
the fact block on a customer story uses, so the two pages read as one system.

The hero's prompt box knows who is looking

Choose a tag to compare

@nastyaeremina nastyaeremina released this 25 Aug 15:39
e24f98c

The hero's prompt box knows who is looking. It said "Get started" to
everyone, including a signed-in customer sitting next to a nav that read "Open
Assembly" — and it sent them to the signup flow, a door they had already walked
through.

Signed in, the button now reads "Open Assembly" and goes to the dashboard,
carrying the typed prompt as a query parameter. Signed out is unchanged: "Get
started", and on to signup with the prompt as before.

Nothing on the dashboard consumes that parameter today — no CTA on this site has
ever carried one to a signed-in destination, so there was no handoff to reuse. A
signed-in visitor lands in their workspace and retypes, which is still the right
room where signup was not. The parameter rides along because an unread one costs
nothing and starts working the day the dashboard grows an entry point for it.

Both labels ship in the markup and the stylesheet picks one before paint, the
way every other auth-aware control here works. Deciding in an effect would
render the signed-out word first and swap it after hydration, in the one spot on
the page a visitor is already looking. The destination is read at click time
instead, where there is no first paint to lose.

Empty clicks go through the same door as typed ones. They used to shortcut
straight to signup, which was harmless while the button always said "Get
started" and became a lie the moment it could say otherwise. Caught in review.

The closing composer takes the same treatment, being the same control further
down the same page.

The homepage's two send buttons match again

Choose a tag to compare

@nastyaeremina nastyaeremina released this 25 Aug 15:05
7244642

The homepage's second send button matches the first. 2026-08-25.3 changed
the hero composer's submit fill to the nav's colour and missed the closing CTA's,
which is the same control further down the same page — so the top box went white
on dark while the bottom one stayed periwinkle.

Both boxes now name the nav's primary in both themes, and no periwinkle submit
fill is left in the tree. Measured against the nav on the homepage: hero and
closing composer both identical in background and text colour, light and dark.

The hero's send button matches the nav

Choose a tag to compare

@nastyaeremina nastyaeremina released this 25 Aug 14:59
d2c45ba

The hero's send button takes the nav button's colour. In dark mode the
prompt box's send pill was the brand periwinkle while "Open Assembly" sat a few
hundred pixels above it in white: two differently treated primaries in one view.
Light had them both near-black already, which is why it took a dark-mode
screenshot to notice. Both fills now name the nav's own, so the pair matches on
background, text colour and radius in either theme.

Light reads var(--color-neutral-900) rather than #171717 — that is what the
nav actually renders, and a shade darker than the hex that had been sitting
there. They looked the same and were not.

The "Unlock more features" popover carries the same label a few pixels away and
deliberately shared the old accent, so it follows the caller's fill rather than
being left as the one periwinkle button on the page.

Not yet auth-aware, deliberately: the button still reads "Get started" to a
signed-in visitor, but it points at signup, so relabelling it without moving the
destination would promise "Build it" and deliver a signup page. Where a
logged-in person should land is a product question. Tracked in WEB3-7.

Also in this release, from main:

  • The homepage "Businesses powered" figure goes from 1,000 to 1,250 (WEB3-5).
  • The accounting client portal hero reads "The accounting client portal you can
    build on" in place of "…your clients won't ignore".
  • The pull request template is rewritten around Summary / Linear / Testing.

A pasted stylesheet stops overriding the blog's design

Choose a tag to compare

@nastyaeremina nastyaeremina released this 25 Aug 12:52
aedb2a2

The comparison table on productized-service-platform looks like the rest of
the blog now.
It had been a bright white panel in the middle of a dark page,
and in either theme it matched nothing else on the site.

Its styling was pasted into Ghost as a <style> block alongside the table:
a fixed white background, a navy header band, uppercase semibold column names,
its own blue for links and green for prices, italic cells, a drop shadow,
Inter. Every one of those is a decision the blog's table design already makes
differently. Dropping the block lets the table fall back to that design, so it
carries our type and palette, follows the theme, and scrolls sideways in place
instead of spilling its column.

Not a restyle of the pasted CSS but a removal, because a <style> element in a
post body is unscoped CSS on our page. This one happened to use its own class
names, so it only wrecked its own table; a paste written with td or a
selectors would have restyled the article around it. Editorial content can no
longer reach the stylesheet at all.

One post in 370 carried such a block, so nothing else on the blog moves. The
table keeps every row, cell and link it had: 11 tools, 44 cells, 22 links, same
prices and destinations.

Measured in each theme separately rather than inferred from one: 51 elements in
dark and 48 in light, none below WCAG AA, lowest ratio 8.67.

Worth saying plainly, since the timing invites the assumption: this was NOT
caused by the pasted-colour fix in 2026-08-25.1. That transform strips
style="…" attributes, and this table was styled by a <style> block, which
the pattern cannot match. The block was still intact on production after that
release. The table had looked this way since the post was published.

Blog tables are readable in dark mode again

Choose a tag to compare

@nastyaeremina nastyaeremina released this 25 Aug 12:38
8626d10

Blog tables are readable in dark mode again. On 19 posts they rendered as
empty rows: the text stayed pure black while the page behind it went black.

Not the table CSS, which uses theme tokens and inverts correctly. The colours
arrived with the content. Those tables were pasted out of Google Docs, which
writes color:#000000 onto every run of text, and an inline style outranks the
stylesheet — so the black survived into dark mode. About 1,500 pieces of text
were pinned that way, plus ~190 to the Google Docs link blue. A few cells were
pinned to white, the same bug facing the other way, unreadable in light mode.

The fix drops color and background-color from pasted styles where a post is
prepared, alongside stripEmoji and liftHeaderNotes, rather than as an
!important override fighting the paste from the stylesheet. Text now inherits
the site's own colours and follows the theme like everything else, and a writer
can keep pasting from Google Docs without it breaking again.

The paste's font and size are deliberately left alone. They are wrong in their
own way but they are legible, and removing them would redraw twenty posts in
both themes rather than fix the one thing that was broken.

Verified on staging across all 21 affected posts: 855 pinned colours before the
deploy, 0 after, with all 76 tables intact. On the worst post, 221 text nodes
measured in each theme and none below WCAG AA.

Found on the way: blog posts 500 on every Vercel preview deployment, which is
why this was verified locally and on staging rather than on the PR preview.
That is pre-existing and unrelated to this change; tracked in WEB3-3.

51 legacy URLs stop 404ing

Choose a tag to compare

@nastyaeremina nastyaeremina released this 24 Aug 20:28
381b8b0

51 legacy URLs stop 404ing. Every path in Marlon's list that this site had
no rule for. LEGACY_REDIRECTS goes 429 to 480 entries, covering old
/apps/directory/* pages, renamed blog posts, the retired /features and
/experts trees, moved docs pages, and one-off campaign paths.

Diffed against the existing table first: 51 missing, nothing conflicting, so
the change is purely additive.

Every source was checked before it was added. Each of the 51 was requested
against production and answered 404, which is the whole point — they are links
somebody has indexed or bookmarked that currently land on nothing. The check
runs the other way too: a source answering 200 is a live page, and redirecting
it would delete that page. That is why four rows from the first batch were left
out. None of these are in that position. Targets were checked as well; all
resolve.

/updates/page/* is written /updates/page/:path*, which is what Next actually
matches. A bare asterisk is a literal, so the row as supplied would have caught
nothing.

Four /experts/* rows land on /experts, which itself redirects to /, so
those are two hops. Left as the list specifies rather than repointed, so the
intent survives if /experts ever becomes a page again.

Verified on staging: all 52 probes, the 51 rows plus a nested wildcard case,
return 308 to exactly the expected target.

One demo button, worded and styled one way

Choose a tag to compare

@nastyaeremina nastyaeremina released this 24 Aug 20:10
2b862cc

Signed-in visitors can book a demo again. The bar offered them one button,
"Open Assembly", and no way to reach the demo form. It now carries "Book a demo"
beside it, and the mobile sheet carries the same pair. Suppressed on the demo
page itself, where it would point at the page you are reading.

Deliberately not behind hideDemo. That flag is passed on every page and
predates the signed-in nav, so honouring it would have made the whole thing a
no-op. It still governs the signed-out bar, which is unchanged.

One secondary treatment, not two. The signed-in demo link shipped as an
outlined button next to a filled primary, while the signed-out side pairs a
plain-text "Log in" with the same filled primary — the same slot wearing two
looks depending on who was looking. Both states now render byte-identical class
strings, in the bar and in the sheet.

One name for the button. The pricing page had reached three — "Book a demo",
"Book demo" and "Contact sales" — for controls that all open the same form.
They read from a single DEMO_CTA_LABEL now, along with the nav and the
rebrand page. "Book a demo" won on the count: the frozen content data already
said it about thirty-five times, and it matches the demo page's own title.

That retired a string comparison against the button's own label, which decided
whether a pricing tier routed to the demo or to signup. Renaming the button —
exactly what this release does — would otherwise have sent the Advanced tier to
signup silently, with no error and no failing build.

Still outstanding from the previous release, and not addressed here: reading the
session cookie in the root layout leaves every page server-rendered rather than
CDN-cached. Tracked in #22.

The signed-in nav finally reads the session cookie

Choose a tag to compare

@nastyaeremina nastyaeremina released this 24 Aug 19:20
ecec339

The signed-in nav actually works now. The dashboard's current-portal-session
cookie is HttpOnly, so the pre-paint script's document.cookie read could never
see it. That is why #16 named the right cookie and still left every visitor on the
signed-out variant. The root layout reads the cookie with cookies() from
next/headers and stamps data-authed on <html> at render time, so a signed-in
visitor gets "Open Assembly" in the header, no Free tier on pricing, and
"Add app to workspace" on the CTAs. The client script is now only the ?authed=1|0
demo override, which still works on preview URLs where no cookie exists.

Known regression, shipping anyway: the whole site is server-rendered now.
Reading a cookie in the root layout opts every route out of static rendering,
because the root layout wraps all of them. The build table went from ~40 static
routes plus 785 prerendered pages to sitemaps and /llm-info alone. Pages no
longer come off the CDN, Ghost is fetched per request instead of per revalidate
window, and Vercel function invocations rise across the site. Nothing is broken
and nothing looks different; it is slower and costs more. The fix is to move the
cookie read somewhere narrower than the root layout, middleware that stamps a
header or a small dynamic component, so only the nav is uncached. Filed as
follow-up work.