ReviewOS

also looking at this

stacks/postline

fix: MVP-readiness fixes — restore lost UI feedback, register safety, analytics error state, mobile + a11y

#31
Merged glennmichael123 wants to merge fix/readiness-batch-1-feedback-timing into main
7 files +123 -29
resources/views/accounts.stxmodified+24-13
Changes to resources/views/accounts.stx
@@ -37,7 +37,7 @@
3737 <span class="text-xs text-zinc-500" data-bluesky-channel-pill>Loading…</span>
3838 </header>
3939 <div class="grid gap-2.5 p-3" data-bluesky-channels>
40 <article class="flex gap-5 items-center justify-between p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="bluesky">
40 <article class="flex flex-col gap-3 justify-between sm:flex-row sm:gap-5 sm:items-center p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="bluesky">
4141 <div>
4242 <h2 class="my-0 font-bold text-[15px]">Bluesky</h2>
4343 <p class="my-1.5 text-[13px] text-slate-700" data-bluesky-channel-handle>Not connected</p>
@@ -45,7 +45,7 @@
4545 </div>
4646 <span class="px-2.5 py-1 font-bold text-[11px] text-zinc-500 whitespace-nowrap border border-zinc-200 rounded-full" data-bluesky-channel-status>Setup</span>
4747 </article>
48 <article class="flex gap-5 items-center justify-between p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="linkedin">
48 <article class="flex flex-col gap-3 justify-between sm:flex-row sm:gap-5 sm:items-center p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="linkedin">
4949 <div>
5050 <h2 class="my-0 font-bold text-[15px]">LinkedIn</h2>
5151 <p class="my-1.5 text-[13px] text-slate-700" data-linkedin-channel-handle>Not connected</p>
@@ -57,7 +57,7 @@
5757 <button class="hidden text-xs text-zinc-500 underline hover:text-zinc-700 transition-colors" type="button" data-linkedin-connect-env>Use token from .env</button>
5858 </div>
5959 </article>
60 <article class="flex gap-5 items-center justify-between p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="instagram">
60 <article class="flex flex-col gap-3 justify-between sm:flex-row sm:gap-5 sm:items-center p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="instagram">
6161 <div>
6262 <h2 class="my-0 font-bold text-[15px]">Instagram</h2>
6363 <p class="my-1.5 text-[13px] text-slate-700" data-instagram-channel-handle>Not connected</p>
@@ -69,7 +69,7 @@
6969 <button class="hidden text-xs text-zinc-500 underline hover:text-zinc-700 transition-colors" type="button" data-instagram-connect-env>Use token from .env</button>
7070 </div>
7171 </article>
72 <article class="flex gap-5 items-center justify-between p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="threads">
72 <article class="flex flex-col gap-3 justify-between sm:flex-row sm:gap-5 sm:items-center p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="threads">
7373 <div>
7474 <h2 class="my-0 font-bold text-[15px]">Threads</h2>
7575 <p class="my-1.5 text-[13px] text-slate-700" data-threads-channel-handle>Not connected</p>
@@ -91,13 +91,13 @@
9191 <span class="px-2.5 py-1 font-bold text-[11px] text-zinc-500 whitespace-nowrap border border-zinc-200 rounded-full" data-mastodon-channel-status>Setup</span>
9292 </div>
9393 <form class="grid gap-2 sm:grid-cols-[minmax(0,1fr)_minmax(0,1.4fr)_auto]" data-mastodon-connect-form>
94 <input class="px-3 h-9 text-neutral-950 text-sm bg-white border border-zinc-200 outline-none rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-100" name="instance" placeholder="mastodon.social" autocomplete="url" data-mastodon-instance required>
95 <input class="px-3 h-9 text-neutral-950 text-sm bg-white border border-zinc-200 outline-none rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-100" name="access_token" type="password" placeholder="Access token" autocomplete="off" data-mastodon-token required>
94 <input class="px-3 h-9 text-neutral-950 text-sm bg-white border border-zinc-200 outline-none rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-100" name="instance" placeholder="mastodon.social" autocomplete="url" aria-label="Mastodon instance" data-mastodon-instance required>
95 <input class="px-3 h-9 text-neutral-950 text-sm bg-white border border-zinc-200 outline-none rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-100" name="access_token" type="password" placeholder="Access token" autocomplete="off" aria-label="Mastodon access token" data-mastodon-token required>
9696 <button class="inline-flex items-center justify-center px-4 h-9 font-bold text-sm text-white bg-neutral-950 hover:bg-neutral-800 rounded-lg disabled:opacity-50 transition-colors" type="submit" data-mastodon-connect-submit>Connect</button>
9797 <p class="sm:col-span-3 m-0 text-xs text-zinc-500">Create a token in your instance's Preferences → Development → New application (needs the <code class="px-1 py-0.5 text-xs bg-zinc-100 rounded">write:statuses</code> + <code class="px-1 py-0.5 text-xs bg-zinc-100 rounded">write:media</code> scopes).</p>
9898 </form>
9999 </article>
100 <article class="flex gap-5 items-center justify-between p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="twitter">
100 <article class="flex flex-col gap-3 justify-between sm:flex-row sm:gap-5 sm:items-center p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="twitter">
101101 <div>
102102 <h2 class="my-0 font-bold text-[15px]">X (Twitter)</h2>
103103 <p class="my-1.5 text-[13px] text-slate-700" data-twitter-channel-handle>Not connected</p>
@@ -126,11 +126,12 @@
126126 const detail = root.querySelector('[data-bluesky-channel-detail]')
127127 const status = root.querySelector('[data-bluesky-channel-status]')
128128 const pill = root.querySelector('[data-bluesky-channel-pill]')
129 const toast = window.postlineToast?.show
130 const blueskyApi = window.postlineBluesky
131
132129 const notify = (message, tone = 'neutral', options = {}) => {
133 if (toast) toast({ message, tone: tone === 'success' ? 'success' : tone === 'error' ? 'error' : 'info', title: options.title })
130 // Resolve the toast helper at call time — it's a deferred module that
131 // loads after this inline script parses, so an eager capture is
132 // undefined on a full page load (e.g. the OAuth redirect back here).
133 const show = window.postlineToast?.show
134 if (show) show({ message, tone: tone === 'success' ? 'success' : tone === 'error' ? 'error' : 'info', title: options.title })
134135 else console.log(message)
135136 }
136137
@@ -162,7 +163,7 @@
162163
163164 const refreshStatus = async () => {
164165 try {
165 const data = await blueskyApi?.fetchStatus()
166 const data = await window.postlineBluesky?.fetchStatus()
166167 renderStatus(data)
167168 }
168169 catch (error) {
@@ -658,7 +659,17 @@
658659 startMastodon()
659660 }
660661
661 void refreshStatus()
662 // Gate the initial status fetch on DOMContentLoaded so the deferred
663 // postline-bluesky module has loaded — otherwise a connected account
664 // renders "Not connected" on a full page load. (Mirrors the other
665 // providers.)
666 const startBluesky = () => void refreshStatus()
667 if (document.readyState === 'loading') {
668 document.addEventListener('DOMContentLoaded', startBluesky, { once: true })
669 }
670 else {
671 startBluesky()
672 }
662673 })()
663674 </script>
664675@endsection