dev vouch dev on at. thats about it atvouch.dev
8
fork

Configure Feed

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

frontend: changes to style, add intro and instructions

Luna 35303dd9 038dff69

+136 -55
+2
.gitignore
··· 32 32 # .vscode/ 33 33 34 34 atvouch 35 + !atvouch/ 35 36 .claude/ 37 + release/
+46 -12
frontend/src/App.css
··· 77 77 line-height: 1.2; 78 78 } 79 79 80 + .intro-block { 81 + display: flex; 82 + gap: 2rem; 83 + margin-bottom: 2rem; 84 + } 85 + 86 + .intro { 87 + color: var(--text); 88 + font-size: 14px; 89 + line-height: 1.7; 90 + flex: 1; 91 + padding-right: 2rem; 92 + border-right: 1px solid var(--border); 93 + } 94 + 95 + .instructions { 96 + color: var(--text); 97 + font-size: 13px; 98 + text-align: right; 99 + line-height: 1.7; 100 + flex: 1; 101 + padding-left: 2rem; 102 + } 103 + 104 + .instructions p { 105 + margin-bottom: 0.25rem; 106 + } 107 + 108 + a { 109 + color: #00e5ff; 110 + text-decoration: none; 111 + } 112 + 113 + a:hover { 114 + text-decoration: underline; 115 + } 116 + 117 + a:visited { 118 + color: #00e5ff; 119 + } 120 + 80 121 /* ── topbar ── */ 81 122 82 123 .topbar { ··· 90 131 } 91 132 92 133 .topbar span { 93 - font-size: 12px; 134 + font-size: 15px; 94 135 color: var(--text-dim); 95 136 letter-spacing: 0.02em; 96 137 text-transform: uppercase; ··· 98 139 99 140 .topbar code { 100 141 font-family: var(--mono); 101 - font-size: 12px; 142 + font-size: 15px; 102 143 color: var(--accent); 103 144 text-transform: none; 104 145 } ··· 173 214 } 174 215 175 216 input[type="text"]::placeholder { 176 - color: var(--text-faint); 217 + color: var(--text-dim); 177 218 font-style: italic; 178 219 font-family: var(--sans); 179 220 font-size: 13px; ··· 274 315 align-items: baseline; 275 316 padding: 0.5rem 0; 276 317 border-bottom: 1px solid var(--border); 277 - font-size: 12px; 278 318 gap: 0.75rem; 279 319 } 280 320 ··· 282 322 border-bottom: none; 283 323 } 284 324 285 - .vouch-list li::before { 286 - content: '\2713'; 287 - color: var(--accent); 288 - font-size: 10px; 289 - margin-right: 0.25rem; 290 - flex-shrink: 0; 291 - } 292 325 293 326 .vouch-handle { 294 327 color: var(--text); 328 + font-size: 15px; 295 329 overflow: hidden; 296 330 text-overflow: ellipsis; 297 331 white-space: nowrap; ··· 299 333 } 300 334 301 335 .vouch-date { 302 - color: var(--text-faint); 336 + color: var(--text-dim); 303 337 font-size: 11px; 304 338 flex-shrink: 0; 305 339 font-variant-numeric: tabular-nums;
+88 -43
frontend/src/App.tsx
··· 1 1 import { useState, useEffect, useCallback, type FormEvent } from "react"; 2 2 import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; 3 - import { initOAuth, startLogin, handleCallback, resumeSession, logout } from "./auth"; 3 + import { 4 + initOAuth, 5 + startLogin, 6 + handleCallback, 7 + resumeSession, 8 + logout, 9 + } from "./auth"; 4 10 import { 5 11 getSessionInfo, 6 12 createVouch, 7 13 listVouches, 8 14 checkVouchPaths, 9 - type SessionInfo, 10 15 type CheckResult, 11 16 type VouchEntry, 12 17 } from "./api"; ··· 42 47 setAgent(null); 43 48 }, [agent]); 44 49 45 - if (loading) return <div className="container"><p>Loading...</p></div>; 50 + if (loading) 51 + return ( 52 + <div className="container"> 53 + <p>Loading...</p> 54 + </div> 55 + ); 46 56 47 57 return ( 48 58 <div className="container"> 49 59 <h1>atvouch</h1> 60 + <div className="intro-block"> 61 + <p className="intro"> 62 + atvouch is a proof of concept for using intentional vouches between 63 + developers to endorse each other. this proof of concept was made over 64 + conversations i was having with some friends regarding agentic coding 65 + and how that changes our trust relationships so there it is. 66 + </p> 67 + <div className="instructions"> 68 + <p>instructions:</p> 69 + <p>1. treat a vouch as an actual endorsement of someone's skills.</p> 70 + <p> 71 + 2. currently this uses{" "} 72 + <a 73 + href="https://www.microcosm.blue/" 74 + target="_blank" 75 + rel="noopener noreferrer" 76 + > 77 + microcosm 78 + </a>{" "} 79 + to resolve the vouch graph, but this may change to a full separate 80 + service if i find this useful. 81 + </p> 82 + <p> 83 + 3. its just one lexicon, one record. there isn't much to this at the 84 + moment. 85 + </p> 86 + </div> 87 + </div> 50 88 {error && <div className="error">{error}</div>} 51 89 {!agent ? ( 52 90 <LoginForm /> ··· 95 133 ); 96 134 } 97 135 98 - function Dashboard({ agent, onLogout }: { agent: OAuthUserAgent; onLogout: () => void }) { 136 + function Dashboard({ 137 + agent, 138 + onLogout, 139 + }: { 140 + agent: OAuthUserAgent; 141 + onLogout: () => void; 142 + }) { 143 + const [handle, setHandle] = useState<string | null>(null); 99 144 const [vouches, setVouches] = useState<VouchEntry[] | null>(null); 100 145 const [vouchesLoading, setVouchesLoading] = useState(true); 101 146 const [vouchesError, setVouchesError] = useState<string | null>(null); 102 147 148 + useEffect(() => { 149 + getSessionInfo(agent) 150 + .then((info) => setHandle(info.handle)) 151 + .catch(() => {}); 152 + }, [agent]); 153 + 103 154 const refreshVouches = useCallback(async () => { 104 155 setVouchesLoading(true); 105 156 setVouchesError(null); ··· 118 169 return ( 119 170 <div> 120 171 <div className="topbar"> 121 - <span>Logged in as <code>{agent.sub}</code></span> 172 + <span> 173 + Logged in as <code>{handle ?? agent.sub}</code> 174 + </span> 122 175 <button onClick={onLogout}>Logout</button> 123 176 </div> 124 177 <div className="layout"> ··· 134 187 {vouches?.map((v) => ( 135 188 <li key={v.did}> 136 189 <span className="vouch-handle">{v.handle ?? v.did}</span> 137 - <span className="vouch-date">{new Date(v.createdAt).toLocaleDateString()}</span> 190 + <span className="vouch-date"> 191 + {new Date(v.createdAt).toISOString().slice(0, 10)} 192 + </span> 138 193 </li> 139 194 ))} 140 195 </ul> 141 196 )} 142 197 </aside> 143 198 <main className="main-panel"> 144 - <MeSection agent={agent} /> 145 199 <CreateVouchSection agent={agent} onVouchCreated={refreshVouches} /> 146 200 <CheckVouchSection agent={agent} /> 147 201 </main> ··· 150 204 ); 151 205 } 152 206 153 - function MeSection({ agent }: { agent: OAuthUserAgent }) { 154 - const [info, setInfo] = useState<SessionInfo | null>(null); 155 - const [loading, setLoading] = useState(false); 156 - const [error, setError] = useState<string | null>(null); 157 - 158 - const fetchMe = async () => { 159 - setLoading(true); 160 - setError(null); 161 - try { 162 - setInfo(await getSessionInfo(agent)); 163 - } catch (err) { 164 - setError(String(err)); 165 - } 166 - setLoading(false); 167 - }; 168 - 169 - return ( 170 - <section> 171 - <h2>Session</h2> 172 - <button onClick={fetchMe} disabled={loading}> 173 - {loading ? "Loading..." : "Show session info"} 174 - </button> 175 - {error && <div className="error">{error}</div>} 176 - {info && ( 177 - <pre>{JSON.stringify(info, null, 2)}</pre> 178 - )} 179 - </section> 180 - ); 181 - } 182 - 183 - function CreateVouchSection({ agent, onVouchCreated }: { agent: OAuthUserAgent; onVouchCreated: () => void }) { 207 + function CreateVouchSection({ 208 + agent, 209 + onVouchCreated, 210 + }: { 211 + agent: OAuthUserAgent; 212 + onVouchCreated: () => void; 213 + }) { 184 214 const [handle, setHandle] = useState(""); 185 215 const [submitting, setSubmitting] = useState(false); 186 216 const [result, setResult] = useState<string | null>(null); ··· 194 224 setResult(null); 195 225 try { 196 226 const res = await createVouch(agent, handle.trim()); 197 - setResult(`Vouched for ${handle.trim()} (${res.subjectDid})\nRecord: ${res.uri}`); 227 + setResult( 228 + `Vouched for ${handle.trim()} (${res.subjectDid})\nRecord: ${res.uri}`, 229 + ); 198 230 setHandle(""); 199 231 onVouchCreated(); 200 232 } catch (err) { ··· 229 261 function CheckVouchSection({ agent }: { agent: OAuthUserAgent }) { 230 262 const [handle, setHandle] = useState(""); 231 263 const [loading, setLoading] = useState(false); 232 - const [result, setResult] = useState<{ data: CheckResult; handle: string } | null>(null); 264 + const [result, setResult] = useState<{ 265 + data: CheckResult; 266 + handle: string; 267 + } | null>(null); 233 268 const [error, setError] = useState<string | null>(null); 234 269 235 270 const onSubmit = async (e: FormEvent) => { ··· 266 301 </div> 267 302 </form> 268 303 {error && <div className="error">{error}</div>} 269 - {result && <CheckResultDisplay result={result.data} handle={result.handle} />} 304 + {result && ( 305 + <CheckResultDisplay result={result.data} handle={result.handle} /> 306 + )} 270 307 </section> 271 308 ); 272 309 } 273 310 274 - function CheckResultDisplay({ result, handle }: { result: CheckResult; handle: string }) { 311 + function CheckResultDisplay({ 312 + result, 313 + handle, 314 + }: { 315 + result: CheckResult; 316 + handle: string; 317 + }) { 275 318 if (result.directVouch) { 276 319 return <pre className="success">you -&gt; {handle}</pre>; 277 320 } ··· 285 328 <p>Found {result.paths.length} vouch route(s):</p> 286 329 <pre> 287 330 {result.paths 288 - .map((path) => path.map((did) => result.handleMap[did] ?? did).join(" -> ")) 331 + .map((path) => 332 + path.map((did) => result.handleMap[did] ?? did).join(" -> "), 333 + ) 289 334 .join("\n")} 290 335 </pre> 291 336 </div>