From 591edf61f8c6f740f9b3cd1b6f7fc81d35173545 Mon Sep 17 00:00:00 2001 From: syui Date: Thu, 8 Jan 2026 17:32:00 +0900 Subject: [PATCH] fix browser --- .claude/settings.local.json | 71 --------------------- .gitignore | 1 + browser/src/App.css | 84 +++++++++++++++++++++++++ browser/src/App.tsx | 60 ++++++++++++++---- browser/src/components/AtUriBrowser.tsx | 70 +++++++++++---------- 5 files changed, 171 insertions(+), 115 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index ab8b7ec..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(cargo init:*)", - "Bash(cargo:*)", - "Bash(find:*)", - "Bash(mkdir:*)", - "Bash(../target/debug/ailog new:*)", - "Bash(../target/debug/ailog build)", - "Bash(/Users/syui/ai/log/target/debug/ailog build)", - "Bash(ls:*)", - "Bash(curl:*)", - "Bash(pkill:*)", - "WebFetch(domain:docs.anthropic.com)", - "WebFetch(domain:github.com)", - "Bash(rm:*)", - "Bash(mv:*)", - "Bash(cp:*)", - "Bash(timeout:*)", - "Bash(grep:*)", - "Bash(./target/debug/ailog:*)", - "Bash(cat:*)", - "Bash(npm install)", - "Bash(npm run build:*)", - "Bash(chmod:*)", - "Bash(./scripts/tunnel.sh:*)", - "Bash(PRODUCTION=true cargo run -- build)", - "Bash(cloudflared tunnel:*)", - "Bash(npm install:*)", - "Bash(./scripts/build-oauth-partial.zsh:*)", - "Bash(./scripts/quick-oauth-update.zsh:*)", - "Bash(../target/debug/ailog serve)", - "Bash(./scripts/test-oauth.sh:*)", - "Bash(./run.zsh:*)", - "Bash(npm run dev:*)", - "Bash(./target/release/ailog:*)", - "Bash(rg:*)", - "Bash(../target/release/ailog build)", - "Bash(zsh run.zsh:*)", - "Bash(hugo:*)", - "WebFetch(domain:docs.bsky.app)", - "WebFetch(domain:syui.ai)", - "Bash(rustup target list:*)", - "Bash(rustup target:*)", - "Bash(git add:*)", - "Bash(git commit:*)", - "Bash(git push:*)", - "Bash(git tag:*)", - "Bash(../bin/ailog:*)", - "Bash(../target/release/ailog oauth build:*)", - "Bash(ailog:*)", - "WebFetch(domain:plc.directory)", - "WebFetch(domain:atproto.com)", - "WebFetch(domain:syu.is)", - "Bash(sed:*)", - "Bash(./scpt/run.zsh:*)", - "Bash(RUST_LOG=debug cargo run -- stream status)", - "Bash(RUST_LOG=debug cargo run -- stream test-api)", - "Bash(/Users/syui/ai/ai/log/target/debug/ailog build)", - "Bash(git reset:*)", - "Bash(./scripts/sync-versions.sh:*)", - "Bash(node:*)", - "Bash(shasum:*)", - "WebFetch(domain:www.pfrazee.com)", - "WebFetch(domain:pfrazee.leaflet.pub)", - "Bash(git checkout:*)", - "Bash(git clone:*)" - ], - "deny": [] - } -} diff --git a/.gitignore b/.gitignore index bd2ddc5..313ed3f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /repos .DS_Store .config +.claude node_modules package-lock.json claude.md diff --git a/browser/src/App.css b/browser/src/App.css index 132ee60..162b582 100644 --- a/browser/src/App.css +++ b/browser/src/App.css @@ -117,3 +117,87 @@ h1 { font-family: inherit; margin: 0; } + +.error-section { + background: #fee; + padding: 1rem; + border-radius: 4px; + margin-bottom: 1rem; + color: #c33; +} + +.records-list { + background: white; + padding: 2rem; + border-radius: 8px; + margin-bottom: 2rem; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.records-list h2 { + font-size: 1.5rem; + margin-bottom: 1rem; + color: #333; +} + +.records-list ul { + list-style: none; + padding: 0; + margin: 0; +} + +.records-list li { + border-bottom: 1px solid #eee; +} + +.records-list li:last-child { + border-bottom: none; +} + +.record-link { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 1rem; + background: none; + border: none; + cursor: pointer; + text-align: left; + transition: background 0.2s; +} + +.record-link:hover { + background: #f5f5f5; +} + +.record-title { + font-size: 1.1rem; + color: #0066cc; + font-weight: 500; +} + +.record-date { + color: #666; + font-size: 0.9rem; +} + +.back-button { + padding: 0.5rem 1rem; + margin-bottom: 1rem; + background: #f5f5f5; + border: 1px solid #ddd; + border-radius: 4px; + cursor: pointer; + font-size: 0.9rem; + color: #666; +} + +.back-button:hover { + background: #eee; +} + +.input-section button:disabled { + background: #ccc; + cursor: not-allowed; +} diff --git a/browser/src/App.tsx b/browser/src/App.tsx index 9106244..a18683c 100644 --- a/browser/src/App.tsx +++ b/browser/src/App.tsx @@ -3,13 +3,13 @@ import { AtUriBrowser, useAtUriBrowser } from './components/AtUriBrowser' import './App.css' function BrowserContent() { - const [atUri, setAtUri] = useState('') - const { navigate, currentRecord } = useAtUriBrowser() + const [handle, setHandle] = useState('') + const { records, currentRecord, setCurrentRecord, loading, error, loadHandle } = useAtUriBrowser() const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - if (atUri) { - await navigate(atUri) + if (handle) { + await loadHandle(handle) } } @@ -20,21 +20,59 @@ function BrowserContent() {
setAtUri(e.target.value)} + placeholder="handle (e.g., ai.syui.ai)" + value={handle} + onChange={(e) => setHandle(e.target.value)} className="at-uri-input" /> - +
- {!currentRecord && ( + {error && ( +
+

Error: {error}

+
+ )} + + {currentRecord ? ( +
+ +

{currentRecord.value.title}

+
+

URI: {currentRecord.uri}

+

Created: {new Date(currentRecord.value.createdAt).toLocaleString()}

+
+
+
{currentRecord.value.content}
+
+
+ ) : records.length > 0 ? ( +
+

Posts ({records.length})

+ +
+ ) : !loading && !error && (

About

Browse AT Protocol records directly from the PDS.

diff --git a/browser/src/components/AtUriBrowser.tsx b/browser/src/components/AtUriBrowser.tsx index 2f643ed..658bdfb 100644 --- a/browser/src/components/AtUriBrowser.tsx +++ b/browser/src/components/AtUriBrowser.tsx @@ -1,11 +1,17 @@ -import { createContext, useContext, ReactNode, useState, useEffect } from 'react' +import { createContext, useContext, ReactNode, useState } from 'react' import { AtProtoClient, RecordResponse } from '../lib/atproto' interface AtUriBrowserContextType { client: AtProtoClient + records: RecordResponse[] + setRecords: (records: RecordResponse[]) => void currentRecord: RecordResponse | null setCurrentRecord: (record: RecordResponse | null) => void - navigate: (uri: string) => Promise + loading: boolean + setLoading: (loading: boolean) => void + error: string | null + setError: (error: string | null) => void + loadHandle: (handle: string) => Promise } const AtUriBrowserContext = createContext(null) @@ -25,46 +31,44 @@ interface AtUriBrowserProps { export function AtUriBrowser({ children, pdsUrl = 'https://syu.is' }: AtUriBrowserProps) { const [client] = useState(() => new AtProtoClient(pdsUrl)) + const [records, setRecords] = useState([]) const [currentRecord, setCurrentRecord] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) - const navigate = async (uri: string) => { + const loadHandle = async (handle: string) => { try { - const parsed = client.parseAtUri(uri) - if (!parsed) { - throw new Error('Invalid AT URI format') - } - - const record = await client.getRecord(parsed.repo, parsed.collection, parsed.rkey) - setCurrentRecord(record) - } catch (error) { - console.error('Failed to navigate:', error) + setLoading(true) + setError(null) setCurrentRecord(null) + + const did = await client.resolveHandle(handle) + const records = await client.listRecords(did, 'ai.syui.log.post') + setRecords(records) + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load records') + setRecords([]) + } finally { + setLoading(false) } } - useEffect(() => { - const params = new URLSearchParams(window.location.search) - const uri = params.get('uri') - if (uri) { - navigate(uri) - } - }, []) - return ( - + {children} - {currentRecord && ( -
-

{currentRecord.value.title}

-
-

URI: {currentRecord.uri}

-

Created: {new Date(currentRecord.value.createdAt).toLocaleString()}

-
-
-
{currentRecord.value.content}
-
-
- )}
) }