A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

Lead the home page with the writing-first experience (/write)

The /write route tested well, so promote it from a hidden parallel route to
the home page's front door: the hero's primary CTA is now "Start writing →"
to /write, with the handle sign-in demoted to a secondary "Already have an
account?" path. Repoint the masthead "Write" button and the account menu's
"Write" item (accountMenuItems) at /write too; the home page no longer links
to the login-gated /editor. Update the prior landing-redesign guard (the new
single "Start writing" CTA is intentional) and record the shift in 0020.

+67 -8
+8
docs/decisions/0020-writing-first-deferred-publish.md
··· 27 27 - Signed-in writers lose eager upload error feedback (errors surface at publish) — accepted for a 28 28 single, simpler media path. 29 29 - `data:`-URL drafts can be large; bytes go to IndexedDB to avoid the `localStorage` quota. 30 + 31 + ## Update — home page leads with /write 32 + The route started as a hidden parallel experience, but it tested well, so the home page now 33 + **leads with it**: the hero's primary CTA is "Start writing →" → `/write`, with the handle 34 + sign-in (`HandleStart`) demoted to a secondary "Already have an account?" path. The masthead 35 + "Write" button and the signed-in account menu's "Write" item (`accountMenuItems`) also point at 36 + `/write` now. `/editor` still exists (edit-an-existing-article + the gated flow) but is no longer 37 + linked from the home page.
+2 -2
src/lib/auth/profile.test.ts
··· 76 76 it( 'returns Dashboard, Write and Profile in order for a profile with a handle', () => { 77 77 expect( accountMenuItems( { ...base, handle: 'jane.bsky.social' } ) ).toEqual( [ 78 78 { label: 'Dashboard', href: '/dashboard' }, 79 - { label: 'Write', href: '/editor' }, 79 + { label: 'Write', href: '/write' }, 80 80 { label: 'Profile', href: '/@jane.bsky.social' }, 81 81 ] ); 82 82 } ); ··· 84 84 it( 'omits the Profile item when no handle is known', () => { 85 85 expect( accountMenuItems( { ...base, handle: null } ) ).toEqual( [ 86 86 { label: 'Dashboard', href: '/dashboard' }, 87 - { label: 'Write', href: '/editor' }, 87 + { label: 'Write', href: '/write' }, 88 88 ] ); 89 89 } ); 90 90 } );
+1 -1
src/lib/auth/profile.ts
··· 55 55 export function accountMenuItems( profile: ViewerProfile ): MenuItem[] { 56 56 const items: MenuItem[] = [ 57 57 { label: 'Dashboard', href: '/dashboard' }, 58 - { label: 'Write', href: '/editor' }, 58 + { label: 'Write', href: '/write' }, 59 59 ]; 60 60 const profileHref = authorPath( profile.handle ); 61 61 if ( profileHref ) {
+5 -3
src/lib/landing/landing-content.test.ts
··· 50 50 describe( 'landing hero redesign', () => { 51 51 const index = read( '../../pages/index.astro' ); 52 52 53 - it( 'mounts the HandleStart island as the primary CTA', () => { 53 + it( 'still mounts the HandleStart island (now the secondary, returning-user sign-in path)', () => { 54 54 expect( index ).toMatch( /import HandleStart from '\.\.\/components\/HandleStart'/ ); 55 55 expect( index ).toMatch( /<HandleStart\s+client:only="react"\s*\/>/ ); 56 56 } ); 57 57 58 - it( 'drops the old multi-button hero actions', () => { 59 - expect( index ).not.toMatch( /Start writing/ ); 58 + it( 'drops the old sample / lexicon / studio hero buttons', () => { 59 + // The single "Start writing → /write" primary CTA is the NEW writing-first design 60 + // (it leads the hero above HandleStart) — distinct from the old multi-button cluster, 61 + // whose sample/lexicon/studio links stay gone. 60 62 expect( index ).not.toMatch( /Read a sample/ ); 61 63 expect( index ).not.toMatch( /See the lexicon/ ); 62 64 expect( index ).not.toMatch( /href="\/editor">Studio/ );
+33
src/pages/_index.write-cta.test.ts
··· 1 + /** 2 + * The home page leads with the writing-first experience (/write), not the gated editor. 3 + * 4 + * Source-level asserts (like the sibling _index.*.test.ts) — rendering the page through 5 + * astro/container isn't viable under the jsdom-pinned runner, so we pin the link targets 6 + * at the source. 7 + */ 8 + import { readFileSync } from 'node:fs'; 9 + import { dirname, join } from 'node:path'; 10 + import { fileURLToPath } from 'node:url'; 11 + import { describe, expect, it } from 'vitest'; 12 + 13 + // Resolve via `fileURLToPath` + `join` (not `new URL(rel, import.meta.url)`): under this 14 + // repo's Astro/Vite-backed Vitest the global `URL` resolves a relative specifier to an 15 + // `http://localhost` dev URL, which `readFileSync` rejects. Mirrors `_write.meta.test.ts`. 16 + const here = dirname( fileURLToPath( import.meta.url ) ); 17 + const src = readFileSync( join( here, './index.astro' ), 'utf8' ); 18 + 19 + describe( 'home page leads with the writing-first experience', () => { 20 + it( 'points the masthead Write button at /write, not the gated editor', () => { 21 + expect( src ).toMatch( /class="[^"]*masthead-write[^"]*"\s+href="\/write"/ ); 22 + // The home page no longer routes anyone to the login-gated /editor. 23 + expect( src ).not.toContain( 'href="/editor"' ); 24 + } ); 25 + 26 + it( 'gives the hero a primary "Start writing" CTA to /write', () => { 27 + expect( src ).toMatch( /href="\/write"[^>]*>\s*Start writing/ ); 28 + } ); 29 + 30 + it( 'keeps the handle sign-in as the secondary, returning-user path', () => { 31 + expect( src ).toContain( '<HandleStart' ); 32 + } ); 33 + } );
+18 -2
src/pages/index.astro
··· 49 49 <header class="masthead"> 50 50 <Logo /> 51 51 <div class="masthead__right"> 52 - <a class="btn btn--ghost masthead-write" href="/editor">Write</a> 52 + <a class="btn btn--ghost masthead-write" href="/write">Write</a> 53 53 <AccountMenu client:only="react" /> 54 54 </div> 55 55 </header> ··· 59 59 <h1 class="hero__title" id="headline" set:html={fallback.headlineHtml} /> 60 60 <p class="hero__lede" id="lede">{fallback.lede}</p> 61 61 <div class="hero__cta"> 62 + <a class="btn btn--primary hero__start" href="/write">Start writing &rarr;</a> 63 + <p class="hero__signin-label">Already have an account?</p> 62 64 <HandleStart client:only="react" /> 63 65 </div> 64 66 <p class="hero__free">Free &amp; open-source. Your words live in your account, not ours.</p> ··· 444 446 color: var(--ink-soft); 445 447 } 446 448 447 - /* ===== Handle input island (lives on the sky) ===== */ 449 + /* ===== Primary CTA + handle input island (live on the sky) ===== */ 448 450 .hero__cta { width: 100%; max-width: 26rem; margin: 2rem auto 0; } 451 + /* Primary "Start writing" action — the writing-first front door. Larger than the default 452 + .btn and lifted off the sky with a soft shadow so it reads at every phase. */ 453 + .hero__start { 454 + font-size: 1.05rem; 455 + padding: 0.8rem 1.6rem; 456 + box-shadow: 0 6px 22px rgba(20, 10, 4, 0.28); 457 + } 458 + /* Demotes the handle sign-in below the primary CTA: it's the returning-user path now. */ 459 + .hero__signin-label { 460 + color: var(--sky-soft); 461 + font-size: 0.85rem; 462 + margin: 1.5rem 0 0.75rem; 463 + text-shadow: var(--sky-shadow); 464 + } 449 465 .hero__free { 450 466 color: var(--sky-soft); 451 467 font-size: 0.9rem;