ai/at
1
0

Compare commits

..

17 Commits

Author SHA1 Message Date
7518cd7d06 fix social-app patch 2026-03-01 17:06:52 +09:00
f130c8d731 fix social-app ios icon 2026-03-01 13:00:39 +09:00
511527abfd fix bgs 2026-02-28 21:12:35 +09:00
31ee74bb66 fix ozone-web patch 2026-02-28 19:43:45 +09:00
acd41f8ece fix social-app ios 2025 2026-02-26 11:09:42 +09:00
34a51a2198 fix readme 2026-02-25 12:43:47 +09:00
966bfc7c2f fix install.zsh 2026-02-25 12:35:14 +09:00
25e7927c70 fix dockerfile copy patch 2026-02-25 08:07:34 +09:00
6d89ea4481 fix patch social-app ios 2026-02-24 20:30:45 +09:00
1690aa4c12 fix install.zsh 2026-02-20 08:43:30 +09:00
c9dd10d6f3 fix dockerfile patch 2026-02-20 07:23:58 +09:00
b6f7d8bed1 fix install.zsh 2026-02-20 07:08:04 +09:00
f4180e0c54 rm atproto 2026-02-19 16:56:10 +09:00
9157948d93 fix social-app ios patch langs 2026-02-19 08:24:32 +09:00
8db064d8c0 fix social-app service link pds 2026-02-16 14:36:12 +09:00
c09738f342 fix readme 2026-02-16 07:33:02 +09:00
0ef8f22dcb fix social-app web support link 2026-02-16 06:50:44 +09:00
64 changed files with 12660 additions and 195 deletions

View File

@@ -33,11 +33,8 @@ jobs:
working-directory: web
- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
directory: web/dist/aiat
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
wranglerVersion: '3'
command: pages deploy web/dist/aiat --project-name=${{ secrets.CLOUDFLARE_PROJECT_NAME }}

2
.gitignore vendored
View File

@@ -9,4 +9,4 @@ k8s/deploy.yml
web/dist
node_modules
package-lock.json
/tmp

View File

@@ -1,18 +1,15 @@
# at
- https://github.com/bluesky-social/atproto
- https://github.com/bluesky-social/atproto/discussions/2026
- https://github.com/bluesky-social/social-app
|word|name|example|
|name|type|example|
|---|---|---|
|at|uri|at://example.com|
|@|user|@example.com|
|[at]proto|repo|`git@github.com:bluesky-social/atproto`|
|[at]mosphere|system|pds, bsky(appview), ozone, bgs, plc|
|[a]uthenticated [t]ransfer|protocol|[did](https://www.w3.org/TR/did-core/)|
- https://atproto.com/guides/glossary
## account
- [ai@syu.is](https://syu.is/profile/did:plc:6qyecktefllvenje24fcxnie)
@@ -22,19 +19,6 @@
```sh
$ curl -sL syu.is/xrpc/_health
# latest
# https://github.com/bluesky-social/atproto/blob/main/packages/pds/package.json
$ curl -sL https://raw.githubusercontent.com/bluesky-social/atproto/refs/heads/main/packages/pds/package.json |jq -r .version
```
```sh
$ handle=ai.syui.ai
$ curl -sL "syu.is/xrpc/com.atproto.repo.describeRepo?repo=${handle}" |jq -r .did
did:plc:6qyecktefllvenje24fcxnie
$ curl -sL "syu.is/xrpc/com.atproto.repo.listRecords?repo=${handle}&collection=app.bsky.feed.post&reverse=true&limit=1"
{"records":[{"uri":"at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.post/3l6s2riuouk2j","cid":"bafyreibjohl7va4upkibw5twaxdd4jg3l6rmfatu4dpjjfd5xkb2ijtlx4","value":{"text":"hello","$type":"app.bsky.feed.post","langs":["ja"],"createdAt":"2024-10-18T13:21:39.809Z"}}],"cursor":"3l6s2riuouk2j"}
```
## feed
@@ -85,4 +69,3 @@ $ curl -sL "syu.is/xrpc/com.atproto.repo.listRecords?repo=${handle}&collection=a
./ios/build.zsh
```

View File

@@ -1,32 +0,0 @@
<svg
viewBox="0 0 2821.6379 794.29016"
preserveAspectRatio="xMidYMid"
xmlns:svg="http://www.w3.org/2000/svg">
<g
transform="matrix(0.1,0,0,-0.1,-282.80153,1445)"
fill="#000000"
stroke="none"
>
<path
d="m 24787,14443 c -4,-3 -7,-224 -7,-490 v -483 h 545 545 v 490 490 h -538 c -296,0 -542,-3 -545,-7 z"
/>
<path
d="m 5190,13285 c -8,-3 -96,-12 -195,-20 -199,-16 -296,-32 -430,-70 -49,-14 -115,-32 -145,-40 -153,-39 -504,-198 -662,-301 -21,-13 -57,-36 -80,-51 -24,-16 -72,-50 -109,-78 -241,-182 -377,-315 -528,-517 -119,-158 -120,-160 -106,-188 23,-45 140,-140 560,-457 110,-84 319,-242 465,-353 146,-110 369,-279 495,-375 791,-600 723,-549 1049,-799 269,-207 398,-307 524,-403 l 114,-86 -49,-49 c -128,-131 -378,-258 -588,-299 -66,-13 -357,-13 -420,0 -115,23 -172,39 -202,54 -18,10 -37,17 -43,17 -24,0 -171,81 -255,141 -50,35 -146,121 -215,192 -69,70 -133,127 -142,127 -19,0 -153,-63 -177,-83 -9,-7 -54,-35 -101,-62 -47,-26 -110,-64 -140,-85 -30,-20 -97,-61 -148,-91 -51,-30 -107,-62 -124,-72 -18,-10 -57,-38 -87,-61 -70,-53 -252,-168 -446,-281 -82,-49 -158,-95 -168,-104 -16,-16 -14,-21 34,-106 165,-289 544,-666 867,-860 9,-6 35,-22 58,-37 36,-23 267,-138 349,-173 108,-47 160,-67 240,-93 150,-48 201,-62 228,-62 14,0 64,-9 109,-19 197,-46 302,-56 573,-56 197,1 281,5 345,17 47,9 110,19 141,22 31,3 75,13 99,21 23,8 56,15 72,15 17,0 51,7 77,15 25,8 80,24 121,36 41,12 125,41 185,66 61,25 124,50 140,56 17,7 89,42 160,78 113,58 177,98 395,246 82,56 273,232 403,371 127,136 237,271 237,291 0,12 -208,179 -425,342 -186,140 -1121,843 -1720,1294 -264,199 -589,444 -723,546 -134,101 -274,208 -312,237 -39,29 -70,58 -70,66 0,21 107,115 203,179 95,64 237,133 295,143 20,4 49,12 63,20 96,48 519,48 619,-1 14,-7 41,-16 60,-20 65,-13 262,-118 360,-191 99,-74 250,-230 372,-384 34,-44 70,-80 80,-80 9,0 39,15 67,33 28,17 70,44 94,58 23,15 121,76 217,136 96,60 254,156 350,213 96,56 272,160 390,230 118,70 230,135 248,144 41,21 41,45 -3,116 -18,30 -46,75 -61,100 -64,106 -252,348 -352,454 -106,112 -169,171 -293,274 -197,164 -310,235 -594,376 -215,106 -519,205 -735,240 -137,22 -574,51 -610,41 z"
/>
<path
d="m 29095,12736 c -421,-44 -744,-157 -975,-342 -259,-207 -396,-446 -465,-809 -16,-84 -23,-301 -14,-425 36,-518 257,-859 694,-1070 166,-80 284,-116 716,-215 449,-103 514,-121 646,-186 218,-107 308,-253 306,-494 -2,-303 -188,-477 -588,-551 -140,-26 -640,-27 -825,-1 -275,38 -486,94 -776,203 -94,35 -174,64 -178,64 -3,0 -6,-175 -6,-389 v -388 l 88,-41 c 404,-187 905,-282 1486,-282 675,0 1154,150 1465,459 196,194 311,434 362,756 20,121 17,476 -5,600 -89,517 -358,800 -945,994 -130,43 -241,71 -616,156 -137,31 -299,73 -360,92 -331,106 -455,246 -455,511 1,249 127,412 387,501 272,92 801,76 1269,-39 116,-28 326,-96 432,-138 l 52,-22 -2,406 -3,406 -66,28 c -154,66 -413,140 -604,174 -279,49 -756,69 -1020,42 z"
/>
<path
d="m 9630,12676 c 0,-2 403,-996 896,-2208 493,-1211 898,-2211 901,-2220 6,-21 -135,-386 -193,-499 -104,-202 -256,-324 -471,-380 -118,-30 -340,-43 -516,-29 -84,6 -185,17 -225,24 -40,7 -75,10 -77,7 -3,-2 -5,-163 -5,-357 v -353 l 63,-30 c 194,-93 493,-138 801,-120 414,23 683,115 937,319 174,140 357,402 474,680 26,62 1945,5159 1945,5167 0,2 -232,2 -516,1 l -517,-3 -556,-1550 c -485,-1350 -560,-1550 -578,-1553 -18,-3 -26,11 -62,105 -23,59 -295,758 -605,1553 l -562,1445 -567,3 c -312,1 -567,0 -567,-2 z"
/>
<path
d="m 15690,10867 c 0,-1970 -1,-1936 56,-2153 128,-497 461,-787 1014,-885 147,-26 440,-36 605,-20 413,40 834,200 1181,447 35,25 73,44 88,44 14,0 26,-1 26,-3 0,-2 20,-94 45,-205 25,-111 45,-204 45,-207 0,-3 209,-5 465,-5 h 465 v 2400 2400 h -535 -535 l -2,-1866 -3,-1866 -115,-50 c -425,-185 -743,-252 -1088,-227 -302,21 -472,109 -562,293 -79,161 -74,11 -77,1969 l -3,1747 h -535 -535 z"
/>
<path
d="m 24785,12668 c -3,-7 -4,-1086 -3,-2398 l 3,-2385 538,-3 537,-2 v 2400 2400 h -535 c -419,0 -537,-3 -540,-12 z"
/>
<path
d="m 21660,8275 v -545 h 565 565 v 545 545 h -565 -565 z"
/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -120,6 +120,10 @@ function at-repos-pull() {
cd ..
fi
done
# Copy feed-generator Dockerfile if missing (removed by git checkout)
if [ ! -f $d/repos/feed-generator/Dockerfile ] && [ -f $d/docker/feed/Dockerfile ];then
cp -rf $d/docker/feed/Dockerfile $d/repos/feed-generator/
fi
cd $d
}
@@ -330,6 +334,41 @@ export const SOCIAL_APP_DOMAIN =\
}' lib/constants.ts 2>/dev/null || true
# Fix parseInt() to handle undefined by adding || ''
sediment "s/parseInt(env('\([^']*\)'))/parseInt(env('\1') || '0')/g" lib/constants.ts 2>/dev/null || true
# Add next-runtime-env to package.json if missing (used by lib/constants.ts)
if ! grep -q 'next-runtime-env' package.json 2>/dev/null; then
echo "📦 Adding next-runtime-env to package.json..."
python3 -c "
import json
with open('package.json') as f:
d = json.load(f)
d.setdefault('dependencies', {})['next-runtime-env'] = '^1.6.2'
with open('package.json', 'w') as f:
json.dump(d, f, indent=2)
f.write('\n')
"
echo "✅ Added next-runtime-env"
fi
# Update @atproto/ozone in service/package.json to latest
echo "📦 Updating @atproto/ozone in service/package.json..."
local latest_ozone_ver
latest_ozone_ver=$(npm view @atproto/ozone version 2>/dev/null)
if [ -n "$latest_ozone_ver" ] && [ -f service/package.json ]; then
python3 -c "
import json, sys
ver = sys.argv[1]
with open('service/package.json') as f:
d = json.load(f)
old = d.get('dependencies', {}).get('@atproto/ozone', '')
d.setdefault('dependencies', {})['@atproto/ozone'] = ver
with open('service/package.json', 'w') as f:
json.dump(d, f, indent=2)
f.write('\n')
print(f'✅ @atproto/ozone: {old} -> {ver}')
" "$latest_ozone_ver"
fi
popd > /dev/null
}
@@ -378,23 +417,86 @@ export const handler = async (ctx: AppContext, params: QueryParams) => {
EOF
echo "✅ Created src/algos/app.ts"
# Restore Dockerfile (removed during patch apply to avoid conflicts)
if [ ! -f $d/repos/feed-generator/Dockerfile ] && [ -f $d/docker/feed/Dockerfile ];then
cp -rf $d/docker/feed/Dockerfile $d/repos/feed-generator/
echo "✅ Restored Dockerfile"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
}
function at-repos-docker-verify() {
local service=$1
local image="at-${service}:latest"
local check_file=""
case $service in
pds) check_file="/app/services/pds/index.js" ;;
bsky) check_file="/app/services/bsky/api.js" ;;
ozone) check_file="/app/services/ozone/api.js" ;;
bgs) check_file="/relay" ;;
plc) check_file="/app/packages/server/dist/index.js" ;;
*) return 0 ;;
esac
local cid
cid=$(docker create --entrypoint "" "$image" true 2>&1)
if [ $? -ne 0 ]; then
echo " ❌ FAILED: cannot create container from $image"
echo " $cid"
return 1
fi
if docker cp "$cid:$check_file" /tmp/.docker-verify-tmp 2>/dev/null; then
rm -f /tmp/.docker-verify-tmp
docker rm "$cid" > /dev/null 2>&1
echo " ✅ Verified: $check_file exists"
return 0
else
rm -f /tmp/.docker-verify-tmp
docker rm "$cid" > /dev/null 2>&1
echo " ❌ FAILED: $check_file not found in $image"
return 1
fi
}
function at-repos-build-docker-atproto() {
cd $d
docker image prune -a
local failed=()
if [ -z "$1" ];then
for ((i=1; i<=${#services}; i++)); do
service=${services[$i]}
docker compose build --no-cache $service
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🔨 Building: $service"
if ! docker compose build --no-cache $service; then
echo " ❌ Build failed: $service"
failed+=($service)
continue
fi
if ! at-repos-docker-verify $service; then
failed+=($service)
continue
fi
if [ "$service" = "ozone" ]; then
docker compose build --no-cache ${service}-web
fi
done
else
docker compose build --no-cache $1
echo "🔨 Building: $1"
if ! docker compose build --no-cache $1; then
echo "❌ Build failed: $1"
return 1
fi
if ! at-repos-docker-verify $1; then
return 1
fi
fi
if [ ${#failed[@]} -gt 0 ]; then
echo ""
echo "❌ Failed builds: ${failed[*]}"
echo "⚠️ Do NOT push these images."
return 1
fi
echo ""
echo "✅ All builds verified successfully."
}
function at-repos-push-reset() {
@@ -412,25 +514,41 @@ function at-repos-push-reset() {
}
function at-repos-push-docker() {
local dtag=$(date +%Y%m%d)
if [ -z "$1" ] || [ "$1" = "push" ]; then
for service in "${services[@]}"; do
if ! at-repos-docker-verify $service; then
echo "⚠️ Skipping push: $service (verification failed)"
continue
fi
docker tag at-${service}:latest localhost:${dport}/${service}:latest
docker tag at-${service}:latest localhost:${dport}/${service}:${dtag}
docker push localhost:${dport}/${service}:latest
docker push localhost:${dport}/${service}:${dtag}
if [ "$service" = "ozone" ]; then
docker tag at-${service}-web:latest localhost:${dport}/${service}-web:latest
docker tag at-${service}-web:latest localhost:${dport}/${service}-web:${dtag}
docker push localhost:${dport}/${service}-web:latest
docker push localhost:${dport}/${service}-web:${dtag}
fi
done
else
docker tag at-${1}:latest localhost:${dport}/${1}:latest
docker push localhost:${dport}/${1}:latest
if ! at-repos-docker-verify $1; then
echo "❌ Push aborted: $1 (verification failed)"
return 1
fi
docker tag at-${1}:latest localhost:${dport}/${1}:latest
docker tag at-${1}:latest localhost:${dport}/${1}:${dtag}
docker push localhost:${dport}/${1}:latest
docker push localhost:${dport}/${1}:${dtag}
fi
echo "📦 Pushed with tags: latest, ${dtag}"
}
function at-repos-pull-docker() {
cd $d
docker image prune -a
docker compose up -d --pull always
echo "💡 Run 'docker image prune' manually to clean up old images."
}
function at-repos-reset-bgs-db() {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 B

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1005 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1005 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 26 KiB

BIN
ios/assets/logo_old.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
ios/assets/logo_white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -27,7 +27,7 @@
policy: 'appVersion',
},
- icon: './assets/app-icons/ios_icon_default_next.png',
+ icon: './assets/logo.png',
+ icon: './assets/app-icon.png',
userInterfaceStyle: 'automatic',
primaryColor: '#1083fe',
newArchEnabled: false,
@@ -43,7 +43,7 @@
- PLATFORM === 'web' // web build doesn't like .icon files
- ? './assets/app-icons/ios_icon_default_next.png'
- : './assets/app-icons/ios_icon_default.icon',
+ icon: './assets/logo.png',
+ icon: './assets/app-icon.png',
infoPlist: {
UIBackgroundModes: ['remote-notification'],
NSCameraUsageDescription:
@@ -61,7 +61,7 @@
},
android: {
- icon: './assets/app-icons/android_icon_default_next.png',
+ icon: './assets/logo.png',
+ icon: './assets/app-icon.png',
adaptiveIcon: {
foregroundImage: './assets/icon-android-foreground.png',
monochromeImage: './assets/icon-android-monochrome.png',

View File

@@ -38,7 +38,7 @@ index 231447b4f..a44b3da05 100644
-const HELP_DESK_LANG = 'en-us'
-export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}`
+const HELP_DESK_LANG = 'ja-jp'
+export const HELP_DESK_URL = '/support/help'
+export const HELP_DESK_URL = '/support/license'
export const EMBED_SERVICE = 'https://embed.bsky.app'
export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js`
export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download'

View File

@@ -1,8 +1,6 @@
diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx
index 9ba067a2f..e34b9f9b0 100644
--- a/src/screens/Settings/AboutSettings.tsx
+++ b/src/screens/Settings/AboutSettings.tsx
@@ -78,7 +78,7 @@ export function AboutSettingsScreen({}: Props) {
@@ -79,7 +79,7 @@
<Layout.Content>
<SettingsList.Container>
<SettingsList.LinkItem
@@ -11,7 +9,7 @@ index 9ba067a2f..e34b9f9b0 100644
label={_(msg`Terms of Service`)}>
<SettingsList.ItemIcon icon={NewspaperIcon} />
<SettingsList.ItemText>
@@ -86,7 +86,7 @@ export function AboutSettingsScreen({}: Props) {
@@ -87,7 +87,7 @@
</SettingsList.ItemText>
</SettingsList.LinkItem>
<SettingsList.LinkItem
@@ -20,11 +18,9 @@ index 9ba067a2f..e34b9f9b0 100644
label={_(msg`Privacy Policy`)}>
<SettingsList.ItemIcon icon={NewspaperIcon} />
<SettingsList.ItemText>
diff --git a/src/screens/Takendown.tsx b/src/screens/Takendown.tsx
index 660aecf1a..f19a62c0f 100644
--- a/src/screens/Takendown.tsx
+++ b/src/screens/Takendown.tsx
@@ -212,10 +212,10 @@ export function Takendown() {
@@ -210,10 +210,10 @@
<Trans>
Your account was found to be in violation of the{' '}
<SimpleInlineLinkText
@@ -38,15 +34,14 @@ index 660aecf1a..f19a62c0f 100644
</SimpleInlineLinkText>
. You have been sent an email outlining the specific violation
and suspension period, if applicable. You can appeal this
diff --git a/src/view/screens/PrivacyPolicy.tsx b/src/view/screens/PrivacyPolicy.tsx
index a89eaadc4..71ce7c81f 100644
--- a/src/view/screens/PrivacyPolicy.tsx
+++ b/src/view/screens/PrivacyPolicy.tsx
@@ -1,51 +1,49 @@
@@ -1,52 +1,49 @@
import React from 'react'
-import {View} from 'react-native'
-import {msg, Trans} from '@lingui/macro'
-import {msg} from '@lingui/core/macro'
-import {useLingui} from '@lingui/react'
-import {Trans} from '@lingui/react/macro'
-import {useFocusEffect} from '@react-navigation/native'
-
-import {usePalette} from '#/lib/hooks/usePalette'
@@ -62,26 +57,25 @@ index a89eaadc4..71ce7c81f 100644
+import {ScrollView} from 'react-native'
import * as Layout from '#/components/Layout'
-import {ViewHeader} from '../com/util/ViewHeader'
-
+import {useSetTitle} from '#/lib/hooks/useSetTitle'
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
-type Props = NativeStackScreenProps<CommonNavigatorParams, 'PrivacyPolicy'>
-export const PrivacyPolicyScreen = (_props: Props) => {
- const pal = usePalette('default')
- const {_} = useLingui()
- const setMinimalShellMode = useSetMinimalShellMode()
-
+export function PrivacyPolicyScreen() {
+ useSetTitle('Privacy Policy')
+ const t = useTheme()
- useFocusEffect(
- React.useCallback(() => {
- setMinimalShellMode(false)
- }, [setMinimalShellMode]),
- )
+import {useSetTitle} from '#/lib/hooks/useSetTitle'
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
+
+export function PrivacyPolicyScreen() {
+ useSetTitle('Privacy Policy')
+ const t = useTheme()
-
return (
<Layout.Screen>
- <ViewHeader title={_(msg`Privacy Policy`)} />
@@ -130,20 +124,19 @@ index a89eaadc4..71ce7c81f 100644
+ </Text>
+
+ <Text style={[a.text_sm, a.mt_xl, {color: t.palette.contrast_500}]}>
+ Last updated: 2025
+ Last updated: 2026
+ </Text>
</ScrollView>
</Layout.Screen>
)
diff --git a/src/view/screens/TermsOfService.tsx b/src/view/screens/TermsOfService.tsx
index d843c713c..c6a36268b 100644
--- a/src/view/screens/TermsOfService.tsx
+++ b/src/view/screens/TermsOfService.tsx
@@ -1,49 +1,49 @@
@@ -1,50 +1,49 @@
import React from 'react'
-import {View} from 'react-native'
-import {msg, Trans} from '@lingui/macro'
-import {msg} from '@lingui/core/macro'
-import {useLingui} from '@lingui/react'
-import {Trans} from '@lingui/react/macro'
-import {useFocusEffect} from '@react-navigation/native'
-
-import {usePalette} from '#/lib/hooks/usePalette'
@@ -159,26 +152,25 @@ index d843c713c..c6a36268b 100644
+import {ScrollView} from 'react-native'
import * as Layout from '#/components/Layout'
-import {ViewHeader} from '../com/util/ViewHeader'
-
+import {useSetTitle} from '#/lib/hooks/useSetTitle'
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
-type Props = NativeStackScreenProps<CommonNavigatorParams, 'TermsOfService'>
-export const TermsOfServiceScreen = (_props: Props) => {
- const pal = usePalette('default')
- const setMinimalShellMode = useSetMinimalShellMode()
- const {_} = useLingui()
-
+export function TermsOfServiceScreen() {
+ useSetTitle('Terms of Service')
+ const t = useTheme()
- useFocusEffect(
- React.useCallback(() => {
- setMinimalShellMode(false)
- }, [setMinimalShellMode]),
- )
+import {useSetTitle} from '#/lib/hooks/useSetTitle'
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
+
+export function TermsOfServiceScreen() {
+ useSetTitle('Terms of Service')
+ const t = useTheme()
-
return (
<Layout.Screen>
- <ViewHeader title={_(msg`Terms of Service`)} />
@@ -225,7 +217,7 @@ index d843c713c..c6a36268b 100644
+ </Text>
+
+ <Text style={[a.text_sm, a.mt_xl, {color: t.palette.contrast_500}]}>
+ Last updated: 2025
+ Last updated: 2026
+ </Text>
</ScrollView>
</Layout.Screen>

View File

@@ -1,8 +1,6 @@
diff --git a/src/view/com/posts/DiscoverFallbackHeader.tsx b/src/view/com/posts/DiscoverFallbackHeader.tsx
index e35a33aaf..a36f84ae0 100644
--- a/src/view/com/posts/DiscoverFallbackHeader.tsx
+++ b/src/view/com/posts/DiscoverFallbackHeader.tsx
@@ -7,37 +7,5 @@ import {TextLink} from '../util/Link'
@@ -7,37 +7,5 @@
import {Text} from '../util/text/Text'
export function DiscoverFallbackHeader() {
@@ -41,18 +39,16 @@ index e35a33aaf..a36f84ae0 100644
- )
+ return null
}
diff --git a/src/view/com/posts/FollowingEmptyState.tsx b/src/view/com/posts/FollowingEmptyState.tsx
index 352cc1dc0..f477521af 100644
--- a/src/view/com/posts/FollowingEmptyState.tsx
+++ b/src/view/com/posts/FollowingEmptyState.tsx
@@ -1,37 +1,14 @@
@@ -1,38 +1,15 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
-import {
- FontAwesomeIcon,
- type FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
import {Trans} from '@lingui/macro'
import {Trans} from '@lingui/react/macro'
-import {useNavigation} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette'
@@ -67,7 +63,7 @@ index 352cc1dc0..f477521af 100644
const pal = usePalette('default')
- const palInverted = usePalette('inverted')
- const navigation = useNavigation<NavigationProp>()
-
- const onPressFindAccounts = React.useCallback(() => {
- if (IS_WEB) {
- navigation.navigate('Search', {})
@@ -80,10 +76,11 @@ index 352cc1dc0..f477521af 100644
- const onPressDiscoverFeeds = React.useCallback(() => {
- navigation.navigate('Feeds')
- }, [navigation])
-
return (
<View style={styles.container}>
@@ -45,36 +22,6 @@ export function FollowingEmptyState() {
<View style={styles.inner}>
@@ -45,36 +22,6 @@
happening.
</Trans>
</Text>
@@ -120,7 +117,7 @@ index 352cc1dc0..f477521af 100644
</View>
</View>
)
@@ -98,13 +45,4 @@ const styles = StyleSheet.create({
@@ -98,13 +45,4 @@
marginLeft: 'auto',
marginRight: 'auto',
},
@@ -134,18 +131,16 @@ index 352cc1dc0..f477521af 100644
- borderRadius: 30,
- },
})
diff --git a/src/view/com/posts/FollowingEndOfFeed.tsx b/src/view/com/posts/FollowingEndOfFeed.tsx
index e3c84d782..efb55d406 100644
--- a/src/view/com/posts/FollowingEndOfFeed.tsx
+++ b/src/view/com/posts/FollowingEndOfFeed.tsx
@@ -1,36 +1,13 @@
@@ -1,37 +1,14 @@
import React from 'react'
import {Dimensions, StyleSheet, View} from 'react-native'
-import {
- FontAwesomeIcon,
- type FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
import {Trans} from '@lingui/macro'
import {Trans} from '@lingui/react/macro'
-import {useNavigation} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette'
@@ -159,7 +154,7 @@ index e3c84d782..efb55d406 100644
const pal = usePalette('default')
- const palInverted = usePalette('inverted')
- const navigation = useNavigation<NavigationProp>()
-
- const onPressFindAccounts = React.useCallback(() => {
- if (IS_WEB) {
- navigation.navigate('Search', {})
@@ -172,10 +167,11 @@ index e3c84d782..efb55d406 100644
- const onPressDiscoverFeeds = React.useCallback(() => {
- navigation.navigate('Feeds')
- }, [navigation])
-
return (
<View
@@ -41,41 +18,8 @@ export function FollowingEndOfFeed() {
style={[
@@ -41,41 +18,8 @@
]}>
<View style={styles.inner}>
<Text type="xl-medium" style={[s.textCenter, pal.text]}>
@@ -183,7 +179,8 @@ index e3c84d782..efb55d406 100644
- You've reached the end of your feed! Find some more accounts to
- follow.
- </Trans>
- </Text>
+ <Trans>You've reached the end of your feed!</Trans>
</Text>
- <Button
- type="inverted"
- style={styles.emptyBtn}
@@ -200,8 +197,7 @@ index e3c84d782..efb55d406 100644
-
- <Text type="xl-medium" style={[s.textCenter, pal.text, s.mt20]}>
- <Trans>You can also discover new Custom Feeds to follow.</Trans>
+ <Trans>You've reached the end of your feed!</Trans>
</Text>
- </Text>
- <Button
- type="inverted"
- style={[styles.emptyBtn, s.mt10]}
@@ -218,7 +214,7 @@ index e3c84d782..efb55d406 100644
</View>
</View>
)
@@ -93,13 +37,4 @@ const styles = StyleSheet.create({
@@ -93,13 +37,4 @@
width: '100%',
maxWidth: 460,
},
@@ -232,11 +228,9 @@ index e3c84d782..efb55d406 100644
- borderRadius: 30,
- },
})
diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx
index 4f25468c9..a72a10b80 100644
--- a/src/view/com/posts/PostFeed.tsx
+++ b/src/view/com/posts/PostFeed.tsx
@@ -766,7 +766,7 @@ let PostFeed = ({
@@ -765,7 +765,7 @@
} else if (row.type === 'feedShutdownMsg') {
return <FeedShutdownMsg feedUri={feedUriOrActorDid} />
} else if (row.type === 'interstitialFollows') {

View File

@@ -1,12 +1,15 @@
diff --git a/src/env/common.ts b/src/env/common.ts
index 04e98c49c..a4ee47932 100644
--- a/src/env/common.ts
+++ b/src/env/common.ts
@@ -107,19 +107,17 @@ export const GCP_PROJECT_ID: number =
@@ -88,7 +88,7 @@
* Metrics API host
*/
export const METRICS_API_HOST: string =
- process.env.EXPO_PUBLIC_METRICS_API_HOST || 'https://events.bsky.app'
+ process.env.EXPO_PUBLIC_METRICS_API_HOST || ''
/**
* URLs for the app config web worker. Can be a
* locally running server, see `env.example` for more.
+ * Disabled for self-hosted environment to avoid CORS errors
* Growthbook API host
@@ -128,9 +128,7 @@
*/
export const GEOLOCATION_DEV_URL = process.env.GEOLOCATION_DEV_URL
export const GEOLOCATION_PROD_URL = `https://ip.bsky.app`
@@ -17,8 +20,7 @@ index 04e98c49c..a4ee47932 100644
/**
* URLs for the live-event config web worker. Can be a
* locally running server, see `env.example` for more.
+ * Disabled for self-hosted environment
@@ -138,9 +136,7 @@
*/
export const LIVE_EVENTS_DEV_URL = process.env.LIVE_EVENTS_DEV_URL
export const LIVE_EVENTS_PROD_URL = `https://live-events.workers.bsky.app`
@@ -26,3 +28,14 @@ index 04e98c49c..a4ee47932 100644
- ? (LIVE_EVENTS_DEV_URL ?? LIVE_EVENTS_PROD_URL)
- : LIVE_EVENTS_PROD_URL
+export const LIVE_EVENTS_URL = null
/**
* URLs for the app-config web worker. Can be a
@@ -148,6 +144,4 @@
*/
export const APP_CONFIG_DEV_URL = process.env.APP_CONFIG_DEV_URL
export const APP_CONFIG_PROD_URL = `https://app-config.workers.bsky.app`
-export const APP_CONFIG_URL = IS_DEV
- ? (APP_CONFIG_DEV_URL ?? APP_CONFIG_PROD_URL)
- : APP_CONFIG_PROD_URL
+export const APP_CONFIG_URL = null

View File

@@ -125,15 +125,15 @@
- <Admonition.Content>
- <Admonition.Text>
- {!isOverAppMinAccessAge ? (
- <Trans>
- You must be {MIN_ACCESS_AGE} years of age or older
- to create an account.
- </Trans>
- <Plural
- value={MIN_ACCESS_AGE}
- other="You must be # years of age or older to create an account."
- />
- ) : (
- <Trans>
- You must be {aaRegionConfig.minAccessAge} years of
- age or older to create an account in your region.
- </Trans>
- <Plural
- value={aaRegionConfig.minAccessAge}
- other="You must be # years of age or older to create an account in your region."
- />
- )}
- </Admonition.Text>
- {IS_NATIVE &&

View File

@@ -1,5 +1,5 @@
--- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx 2026-02-16 03:00:07
+++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx 2026-02-16 03:02:25
--- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx
+++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx
@@ -1,5 +1,5 @@
import {memo, useMemo, useState} from 'react'
-import {View} from 'react-native'
@@ -7,54 +7,63 @@
import {
type AppBskyActorDefs,
moderateProfile,
@@ -9,9 +9,11 @@
} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
@@ -11,7 +11,10 @@
import {useLingui} from '@lingui/react'
+import {useQuery} from '@tanstack/react-query'
import {Trans} from '@lingui/react/macro'
import {useActorStatus} from '#/lib/actor-status'
+import {useQuery} from '@tanstack/react-query'
+
import {useHaptics} from '#/lib/haptics'
+import {useOpenLink} from '#/lib/hooks/useOpenLink'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {logger} from '#/logger'
@@ -20,7 +22,7 @@
useProfileBlockMutationQueue,
useProfileFollowMutationQueue,
} from '#/state/queries/profile'
-import {useRequireAuth, useSession} from '#/state/session'
+import {useAgent, useRequireAuth, useSession} from '#/state/session'
import {ProfileMenu} from '#/view/com/profile/ProfileMenu'
import {atoms as a, platform, useBreakpoints, useTheme} from '#/alf'
import {SubscribeProfileButton} from '#/components/activity-notifications/SubscribeProfileButton'
@@ -45,6 +47,83 @@
import {ProfileHeaderMetrics} from './Metrics'
@@ -47,6 +50,103 @@
import {ProfileHeaderShell} from './Shell'
import {ProfileHeaderSuggestedFollows} from './SuggestedFollows'
+
+const SERVICE_FAVICONS: Record<string, any> = {
+ 'syui.ai': require('../../../../assets/favicons/syui.ai.png'),
+ 'bsky.app': require('../../../../assets/favicons/bsky.app.png'),
+ 'atproto.com': require('../../../../assets/favicons/atproto.com.png'),
+}
+
+async function resolvePds(did: string): Promise<string> {
+ if (did.startsWith('did:web:')) {
+ const host = did.split(':').slice(2).join(':')
+ const res = await fetch(`https://${host}/.well-known/did.json`)
+ if (!res.ok) throw new Error('failed to resolve did:web')
+ const doc = await res.json()
+ const pds = doc.service?.find((s: any) => s.id === '#atproto_pds')?.serviceEndpoint
+ if (pds) return pds
+ return `https://${host}`
+ }
+ const res = await fetch(`https://plc.directory/${did}`)
+ if (!res.ok) throw new Error('failed to resolve DID')
+ const doc = await res.json()
+ const pds = doc.service?.find((s: any) => s.id === '#atproto_pds')?.serviceEndpoint
+ if (!pds) throw new Error('no PDS found')
+ return pds
+}
+
+function ProfileServiceLinks({
+ profile,
+}: {
+ profile: AppBskyActorDefs.ProfileViewDetailed
+}) {
+ const t = useTheme()
+ const agent = useAgent()
+ const openLink = useOpenLink()
+
+ const {data: services} = useQuery({
+ queryKey: ['profile-services', profile.did],
+ queryFn: async () => {
+ const res = await agent.com.atproto.repo.describeRepo({
+ repo: profile.did,
+ })
+ const collections: string[] = res.data.collections || []
+ const pds = await resolvePds(profile.did)
+ const res = await fetch(
+ `${pds}/xrpc/com.atproto.repo.describeRepo?repo=${encodeURIComponent(profile.did)}`,
+ )
+ if (!res.ok) throw new Error('failed')
+ const data = await res.json()
+ const collections: string[] = data.collections || []
+ const serviceSet = new Set<string>()
+ for (const nsid of collections) {
+ const parts = nsid.split('.')
@@ -109,10 +118,11 @@
+ </View>
+ )
+}
+
interface Props {
profile: AppBskyActorDefs.ProfileViewDetailed
@@ -151,6 +230,7 @@
descriptionRT: RichTextAPI | null
@@ -152,6 +252,7 @@
{!isPlaceholderProfile && !isBlockedUser && (
<View style={a.gap_md}>
<ProfileHeaderMetrics profile={profile} />

View File

@@ -1,6 +1,6 @@
--- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx
+++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx
@@ -46,6 +46,7 @@
@@ -48,6 +48,7 @@
import {ProfileHeaderHandle} from './Handle'
import {ProfileHeaderMetrics} from './Metrics'
import {ProfileHeaderShell} from './Shell'
@@ -8,7 +8,7 @@
import {ProfileHeaderSuggestedFollows} from './SuggestedFollows'
const SERVICE_FAVICONS: Record<string, any> = {
@@ -231,6 +232,7 @@
@@ -253,6 +254,7 @@
<View style={a.gap_md}>
<ProfileHeaderMetrics profile={profile} />
<ProfileServiceLinks profile={profile} />

View File

@@ -0,0 +1,19 @@
--- a/src/view/shell/desktop/RightNav.tsx
+++ b/src/view/shell/desktop/RightNav.tsx
@@ -111,14 +111,14 @@
</>
)}
<InlineLinkText
- to="https://bsky.social/about/support/privacy-policy"
+ to="/support/privacy-policy"
style={[t.atoms.text_contrast_medium]}
label={_(msg`Privacy`)}>
{_(msg`Privacy`)}
</InlineLinkText>
<Text style={[t.atoms.text_contrast_low]}>{' ∙ '}</Text>
<InlineLinkText
- to="https://bsky.social/about/support/tos"
+ to="/support/tos"
style={[t.atoms.text_contrast_medium]}
label={_(msg`Terms`)}>
{_(msg`Terms`)}

View File

@@ -0,0 +1,60 @@
--- a/src/view/com/auth/SplashScreen.tsx
+++ b/src/view/com/auth/SplashScreen.tsx
@@ -2,6 +2,7 @@
import {Image as RNImage, View} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
import {Image} from 'expo-image'
+import {useVideoPlayer, VideoView} from 'expo-video'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -15,10 +16,13 @@
import splashImagePointer from '../../../../assets/splash/illustration-mobile.png'
// @ts-ignore
import darkSplashImagePointer from '../../../../assets/splash/illustration-mobile-dark.png'
+// @ts-ignore
+import splashVideoPointer from '../../../../assets/splash/illustration-mobile.mp4'
const splashImageUri = RNImage.resolveAssetSource(splashImagePointer).uri
const darkSplashImageUri = RNImage.resolveAssetSource(
darkSplashImagePointer,
).uri
+const splashVideoUri = RNImage.resolveAssetSource(splashVideoPointer).uri
export const SplashScreen = ({
onPressSignin,
@@ -53,13 +57,30 @@
}
}, [t, isDarkMode])
+ const player = useVideoPlayer(splashVideoUri, p => {
+ p.loop = true
+ p.muted = true
+ p.play()
+ })
+
return (
<>
- <Image
- accessibilityIgnoresInvertColors
- source={{uri: isDarkMode ? darkSplashImageUri : splashImageUri}}
- style={[a.absolute, a.inset_0]}
- />
+ {isDarkMode ? (
+ <Image
+ accessibilityIgnoresInvertColors
+ source={{uri: darkSplashImageUri}}
+ style={[a.absolute, a.inset_0]}
+ />
+ ) : (
+ <VideoView
+ player={player}
+ style={[a.absolute, a.inset_0]}
+ contentFit="cover"
+ nativeControls={false}
+ allowsFullscreen={false}
+ allowsPictureInPicture={false}
+ />
+ )}
<Animated.View
entering={FadeIn.duration(90)}

View File

@@ -0,0 +1,40 @@
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -717,16 +717,7 @@
post.shortenedGraphemeLength > 0 || post.embed.media || post.embed.link,
)
- // Show discard prompt if there's content AND either:
- // - No draft is loaded (new composition)
- // - Draft is loaded but has been modified
- if (hasContent && (!composerState.draftId || composerState.isDirty)) {
- closeAllDialogs()
- Keyboard.dismiss()
- discardPromptControl.open()
- } else {
- onClose()
- }
+ onClose()
}, [
thread,
composerState.draftId,
@@ -1551,18 +1542,7 @@
</>
) : (
<>
- {!isReply && (
- <DraftsButton
- onSelectDraft={onSelectDraft}
- onSaveDraft={onSaveDraft}
- onDiscard={onDiscard}
- isEmpty={isEmpty}
- isDirty={isDirty}
- isEditingDraft={isEditingDraft}
- canSaveDraft={canSaveDraft}
- textLength={textLength}
- />
- )}
+ {/* DraftsButton removed */}
<Button
testID="composerPublishBtn"
label={

View File

@@ -0,0 +1,55 @@
diff --git a/src/features/liveEvents/context.tsx b/src/features/liveEvents/context.tsx
index 3de2534a8..e11955f57 100644
--- a/src/features/liveEvents/context.tsx
+++ b/src/features/liveEvents/context.tsx
@@ -24,6 +24,7 @@ export const DEFAULT_LIVE_EVENTS = {
async function fetchLiveEvents(): Promise<LiveEventsWorkerResponse | null> {
try {
+ if (!LIVE_EVENTS_URL) return null
const res = await fetch(`${LIVE_EVENTS_URL}/config`)
if (!res.ok) return null
const data = await res.json()
diff --git a/src/geolocation/const.ts b/src/geolocation/const.ts
index 653e829ba..4992cc870 100644
--- a/src/geolocation/const.ts
+++ b/src/geolocation/const.ts
@@ -1,7 +1,9 @@
import {GEOLOCATION_URL} from '#/env'
import {type Geolocation} from '#/geolocation/types'
-export const GEOLOCATION_SERVICE_URL = `${GEOLOCATION_URL}/geolocation`
+export const GEOLOCATION_SERVICE_URL = GEOLOCATION_URL
+ ? `${GEOLOCATION_URL}/geolocation`
+ : null
/**
* Default geolocation config.
diff --git a/src/geolocation/service.ts b/src/geolocation/service.ts
index 2d9285b67..c7c3d02c0 100644
--- a/src/geolocation/service.ts
+++ b/src/geolocation/service.ts
@@ -26,9 +26,10 @@ const onGeolocationServiceResponseUpdate = (
}
async function fetchGeolocationServiceData(
- url: string,
+ url: string | null,
): Promise<Geolocation | undefined> {
if (debug.enabled) return debug.resolve(debug.geolocation)
+ if (!url) throw new Error('geolocation service disabled')
const res = await fetch(url)
if (!res.ok) {
throw new Error(`fetchGeolocationServiceData failed ${res.status}`)
diff --git a/src/state/appConfig.tsx b/src/state/appConfig.tsx
index 67b0e553e..9eacf7ead 100644
--- a/src/state/appConfig.tsx
+++ b/src/state/appConfig.tsx
@@ -30,6 +30,7 @@ let fetchAppConfigPromise: Promise<AppConfigResponse> | undefined
async function fetchAppConfig(): Promise<AppConfigResponse | null> {
try {
+ if (!APP_CONFIG_URL) return null
if (!fetchAppConfigPromise) {
fetchAppConfigPromise = (async () => {
const r = await fetch(`${APP_CONFIG_URL}/config`)

View File

@@ -27,7 +27,7 @@ export function LicenseScreen() {
<Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>MIT License</Text>
<Text style={[a.mb_md, {fontFamily: 'monospace'}]}>
Copyright (c) 2022-2025 Bluesky PBC
Copyright (c) 2022-2026 Bluesky PBC
</Text>
<Text style={[a.mb_md]}>

View File

@@ -4,7 +4,6 @@ import {type AppBskyActorDefs} from '@atproto/api'
import {useQuery} from '@tanstack/react-query'
import {useOpenLink} from '#/lib/hooks/useOpenLink'
import {useAgent} from '#/state/session'
import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography'
import {createSinglePathSVG} from '#/components/icons/TEMPLATE'
@@ -66,6 +65,26 @@ const SERVICE_CONFIG: Record<
},
}
// --- PDS Resolution ---
async function resolvePds(did: string): Promise<string> {
if (did.startsWith('did:web:')) {
const host = did.split(':').slice(2).join(':')
const res = await fetch(`https://${host}/.well-known/did.json`)
if (!res.ok) throw new Error('failed to resolve did:web')
const doc = await res.json()
const pds = doc.service?.find((s: any) => s.id === '#atproto_pds')?.serviceEndpoint
if (pds) return pds
return `https://${host}`
}
const res = await fetch(`https://plc.directory/${did}`)
if (!res.ok) throw new Error('failed to resolve DID')
const doc = await res.json()
const pds = doc.service?.find((s: any) => s.id === '#atproto_pds')?.serviceEndpoint
if (!pds) throw new Error('no PDS found')
return pds
}
// --- Component ---
export function ProfileAtLinks({
@@ -74,18 +93,18 @@ export function ProfileAtLinks({
profile: AppBskyActorDefs.ProfileViewDetailed
}) {
const t = useTheme()
const agent = useAgent()
const openLink = useOpenLink()
const {data: linkData} = useQuery({
queryKey: ['at-links', profile.did],
queryFn: async () => {
const res = await agent.com.atproto.repo.getRecord({
repo: profile.did,
collection: 'ai.syui.at.link',
rkey: 'self',
})
return res.data.value as LinkCollection
const pds = await resolvePds(profile.did)
const res = await fetch(
`${pds}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(profile.did)}&collection=ai.syui.at.link&rkey=self`,
)
if (!res.ok) throw new Error('not found')
const json = await res.json()
return json.value as LinkCollection
},
retry: false,
staleTime: 1000 * 60 * 5,

12162
ios/patching/ja-messages.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -50,6 +50,10 @@ PATCH_FILES_IOS=(
"040-social-app-ios-hide-composer-prompt.patch"
"041-social-app-ios-splash-signin-button.patch"
"042-social-app-ios-at-links.patch"
"043-social-app-ios-rightnav-links.patch"
"044-social-app-ios-splash-video.patch"
"045-social-app-ios-composer-cancel.patch"
"046-social-app-ios-null-url-guards.patch"
)
function ios-env() {
@@ -171,6 +175,17 @@ function ios-copy-new-files() {
echo "✅ Copied all assets (including logo.png, app-icons)"
fi
# Generate app-icon.png (logo on #333 background) for app icon
if [ -f "$d/ios/assets/logo.png" ]; then
if command -v magick >/dev/null 2>&1; then
magick -size 1024x1024 "xc:#333333" "$d/ios/assets/logo.png" -gravity center -composite "$target_dir/assets/app-icon.png"
echo "✅ Generated app-icon.png (logo + #333 bg)"
else
echo "⚠️ ImageMagick not found, copying logo.png as app-icon.png fallback"
cp "$d/ios/assets/logo.png" "$target_dir/assets/app-icon.png"
fi
fi
# Copy License.tsx
if [ -f "$patching_dir/License.tsx" ]; then
mkdir -p "$target_dir/src/view/screens"
@@ -185,6 +200,18 @@ function ios-copy-new-files() {
echo "✅ Copied AppInfo.tsx"
fi
# Copy Japanese locale (translation overrides)
if [ -f "$patching_dir/ja-messages.po" ]; then
cp "$patching_dir/ja-messages.po" "$target_dir/src/locale/locales/ja/messages.po"
echo "✅ Copied ja-messages.po"
fi
# Copy splash video
if [ -f "$d/ios/assets/splash/illustration-mobile.mp4" ]; then
cp "$d/ios/assets/splash/illustration-mobile.mp4" "$target_dir/assets/splash/illustration-mobile.mp4"
echo "✅ Copied splash video (illustration-mobile.mp4)"
fi
# Copy ProfileAtLinks.tsx
if [ -f "$patching_dir/ProfileAtLinks.tsx" ]; then
cp "$patching_dir/ProfileAtLinks.tsx" "$target_dir/src/screens/Profile/Header/ProfileAtLinks.tsx"
@@ -203,6 +230,14 @@ function ios-copy-new-files() {
echo "✅ Copied favicons to bskyweb/static"
fi
# Compile locale if messages.po was copied
if [ -f "$patching_dir/ja-messages.po" ]; then
echo "🌐 Compiling locale..."
pushd "$target_dir" > /dev/null
yarn intl:compile 2>/dev/null && echo "✅ Compiled locale" || echo "⚠️ Locale compile skipped (yarn not ready)"
popd > /dev/null
fi
echo ""
}

View File

@@ -7,12 +7,12 @@ path: help/index.html
## Getting Started
1. Download the app from the App Store
2. Sign in with your AT Protocol handle
2. Sign in with your handle
3. Start connecting with your community
## Authentication
This app uses AT Protocol OAuth for secure authentication. Your credentials are never stored on our servers.
This app uses OAuth for secure authentication. Your credentials are never stored on our servers.
## Features
@@ -36,4 +36,4 @@ This app uses AT Protocol OAuth for secure authentication. Your credentials are
## Contact Support
For additional help, please visit our Git repository or contact us through AT Protocol.
For additional help, please visit our Git repository or contact us through our social account.

View File

@@ -12,7 +12,7 @@ This application is open source software.
This app uses the following open source libraries:
- **AT Protocol** - MIT License
- **Decentralized Social Protocol** - MIT License
- **React Native** - MIT License
- **Expo** - MIT License
@@ -22,7 +22,7 @@ App icons and graphics are proprietary unless otherwise noted.
## Attribution
Built with AT Protocol by syui.
Built by syui.
## Contact

View File

@@ -8,7 +8,7 @@ path: privacy/index.html
This application collects minimal information necessary for its operation:
- **Account Information**: Your AT Protocol handle and DID for authentication
- **Account Information**: Your handle and DID for authentication
- **Usage Data**: Basic app usage statistics to improve the service
## How We Use Your Information
@@ -19,7 +19,7 @@ This application collects minimal information necessary for its operation:
## Data Storage
Your data is stored securely on AT Protocol compatible servers. We do not sell or share your personal information with third parties.
Your data is stored securely on decentralized social servers. We do not sell or share your personal information with third parties.
## Your Rights

View File

@@ -12,7 +12,7 @@ By using this application, you agree to these Terms of Service.
You agree to use this service in accordance with:
- Applicable laws and regulations
- AT Protocol community guidelines
- Community guidelines
- Respectful behavior towards other users
## User Content

View File

@@ -5,7 +5,7 @@
"scheme": "aiat",
"version": "1.111.0",
"category": "Social",
"description": "Aiat is a social networking application based on AT Protocol. Connect with your community on syu.is.",
"description": "Aiat is a social networking application. Connect with your community on syu.is.",
"backLink": "syu.is",
"icon": "/static/app.png",
"os": "iOS 26.0+",

View File

@@ -61,7 +61,7 @@ export function appLayout(site: SiteConfig, content: string): string {
<span class="link-arrow">&rarr;</span>
</div>
<div class="link-row">
<span class="link-icon">ATProto</span>
<span class="link-icon">Social</span>
<a href="https://syui.ai" class="link-value" target="_blank">syui.ai</a>
<span class="link-arrow">&rarr;</span>
</div>