Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
ed3f965
ci: setup survives npm registry outages (#23582)
t3dotgg Oct 22, 2026
f054d1b
feat(web,mobile): messages from other agents fold into one-line rows …
shivamhwp Oct 22, 2026
60b35a1
fix(web): subagent bar keeps model and status readable in narrow chat…
shivamhwp Oct 22, 2026
f055995
fix(chat): mark tool images beyond the preview limit (#23595)
only21mil Oct 22, 2026
b9aaff4
fix(web): provider updates no longer pop toasts on launch (#23233)
flamboh Oct 22, 2026
f460ddb
fix(server): probe the Windows startup environment asynchronously (#2…
UtkarshUsername Oct 22, 2026
5e8c5eb
fix(usage): count initial Codex requests with inherited totals (#23729)
PixPMusic Oct 22, 2026
4eae8ae
chore(mobile): bump app version to 2.1.0
t3-code-bot[bot] Oct 22, 2026
e54e71d
fix(web): let toast content use the full accordion width (#23777)
Yash-Singh1 Oct 22, 2026
044f072
fix(web): sidebar follows the active thread after history navigation …
juliusmarminge Oct 22, 2026
fd1063b
fix(server): keep subagent transcript updates off shell stream (#23783)
Yash-Singh1 Oct 22, 2026
22840ab
fix(deps): pin all patched package versions (#23798)
juliusmarminge Oct 22, 2026
52c2b38
docs: document the GitCafe mirror and refresh the dev setup (#23808)
juliusmarminge Oct 22, 2026
d7f3b72
fix: no more em dashes in product copy (#23799)
t3dotgg Oct 22, 2026
ef92441
chore(release): prepare v0.1.2
t3-code-bot[bot] Oct 22, 2026
2c8bdf6
chore(mobile): bump Clerk Expo SDKs to the new native client sync (#2…
wobsoriano Oct 22, 2026
ff31a2a
fix(mobile): capture folded and unfolded Duo store screenshots (#23849)
juliusmarminge Oct 22, 2026
cf3acd9
fix(server): preserve typed requirements as the RPC group grows (#23813)
juliusmarminge Oct 22, 2026
b3c0109
fix(server): wake PR watches when no checks report (#23863)
juliusmarminge Oct 22, 2026
11adcb0
fix(server): resume Codex parents after native children finish (#23840)
juliusmarminge Oct 22, 2026
f9c1d02
fix(gitcafe): authored lists, merge progress, and author reviews matc…
juliusmarminge Oct 22, 2026
52bb5c8
fix(forgejo): merges follow the repository's delete-branch setting (#…
juliusmarminge Oct 22, 2026
200dad3
fix(web): keep PR check counts in sync (#23548)
juliusmarminge Oct 22, 2026
c7e3993
fix(server): speed up shell snapshots on large databases (#23751)
Yash-Singh1 Oct 22, 2026
478fdfc
fix(server): keep managed Codex token renewal in the server (#21786)
juliusmarminge Oct 22, 2026
f5cf9a0
fix(server): renew active managed Codex turns through native auth (#2…
juliusmarminge Oct 22, 2026
e78297d
feat(web): telemetry export settings show where the running endpoint …
yordis Oct 22, 2026
23bd753
fix(ui): preserve tool icon detail in light mode (#23896)
juliusmarminge Oct 22, 2026
1c8e79e
fix(mcp): accept GitCafe pull request URLs in link/unlink/watch tools…
juliusmarminge Oct 23, 2026
5127302
feat(settings): add thread title instructions (#21882)
CadenEl Oct 23, 2026
18e2742
fix(mobile): undo toasts keep their action on one row (#23910)
saphid Oct 23, 2026
50e151c
docs(connect): correct the relay webhook endpoint key (#23526)
juliusmarminge Oct 23, 2026
05cd55d
fix(mobile): center Android composer text and pad wrapped drafts (#23…
PixPMusic Oct 23, 2026
09bb9f1
fix(server): wake PR watches when remaining CI checks finish (#23904)
juliusmarminge Oct 23, 2026
a8dba9c
fix(mobile): the empty composer row shows only the provider icon (#23…
flamboh Oct 23, 2026
6ed47b5
fix(server): route SQLite reads around writer contention (#23902)
Yash-Singh1 Oct 23, 2026
b7e802b
fix(claude): route approval feedback to the requesting agent (#23657)
Yash-Singh1 Oct 23, 2026
0c25c29
fix(web): align pull request file counts with diff stats (#23905)
Yash-Singh1 Oct 23, 2026
9b7e7df
fix(web): task durations over an hour no longer wrap in the tasks pan…
jamesvillarrubia Oct 23, 2026
53245cc
fix(server): preserve Codex sign-in guidance in usage notices (#17055)
jieyuexing Oct 23, 2026
7e52513
fix(server): restore Claude subscription usage through proxies (#12069)
Kanaliseren Oct 23, 2026
71a283a
fix(server): Forgejo skipped jobs no longer keep a pull request watch…
Mnigos Oct 23, 2026
a68e6e9
fix(mobile): move Android undo toasts above bottom navigation (#23928)
PixPMusic Oct 23, 2026
9a63802
perf(server): skip unused Git output line parsing (#23953)
juliusmarminge Oct 23, 2026
e64a6d4
fix(server): abort interrupted ACP MCP fetches (#23955)
juliusmarminge Oct 23, 2026
56c365c
fix(web): new threads no longer hang when a project's machine is offl…
t3dotgg Oct 23, 2026
6d9ec7b
fix(pi): stop blank input placeholders from failing the run (#23235)
only21mil Oct 23, 2026
656f3f4
fix(cursor): fail turns that end in a transport error after commentar…
only21mil Oct 23, 2026
49597c7
test(forgejo): answer the base branch read in the skipped-checks test…
juliusmarminge Oct 23, 2026
807319c
fix(opencode): retry failed model catalogs until they recover (#23939)
Yash-Singh1 Oct 23, 2026
d7a8d6f
fix(server): avoid full shell read on resume (#23987)
Yash-Singh1 Oct 23, 2026
b4ab61c
merge upstream/main into fork
adampeterhiggins Oct 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Dev container for T3 Code. Mirrors CI (ubuntu-24.04, Node 24, Rust stable)
// Dev container for T3 Code. Mirrors CI (ubuntu-24.04, Node 26, Rust stable)
// and the canonical setup in docs/operations/development.md: global `vp`, `vp i`.
// Contributor doc: docs/internals/devcontainer.md
{
Expand All @@ -8,7 +8,7 @@
// nodeGypDependencies (default true) brings python3/make/g++, which Linux
// needs for node-pty's node-gyp fallback (its prebuilds are mac/win only).
"ghcr.io/devcontainers/features/node:2": {
"version": "24"
"version": "26"
},
// native/resource-monitor (edition 2024, needs stable >= 1.85). The server
// degrades gracefully without the binary, but CI checks cargo fmt + test.
Expand Down
22 changes: 19 additions & 3 deletions .github/actions/setup-ci-deps/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,38 @@ description: Install Vite+ and the workspace dependencies from cache.
runs:
using: composite
steps:
# setup-vp reuses an installed exact version, so restoring its home skips the
# installer download on every job.
# ~/.vite-plus holds vp, the Node.js runtime, and pnpm. A cache entry never
# updates, so the key names all three versions; otherwise a pnpm or Node.js
# bump makes every job download them from npm again.
- name: Resolve Vite+ version
id: vp
shell: bash
run: |
echo "version=$(sed -nE 's/^ vite-plus: ([0-9][^ ]*)$/\1/p' pnpm-workspace.yaml)" >> "$GITHUB_OUTPUT"
echo "node=$(jq -r '.engines.node' package.json)" >> "$GITHUB_OUTPUT"
echo "pnpm=$(jq -r '.packageManager' package.json)" >> "$GITHUB_OUTPUT"

- name: Cache Vite+
id: vp_cache
if: steps.vp.outputs.version != ''
uses: actions/cache@v6
with:
path: ~/.vite-plus
key: vite-plus-home-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.vp.outputs.version }}
key: vite-plus-home-v2-${{ runner.os }}-${{ runner.arch }}-${{ steps.vp.outputs.version }}-node${{ steps.vp.outputs.node }}-${{ steps.vp.outputs.pnpm }}

# setup-vp always runs the installer, which fetches vp from npm even when
# this exact version is installed. A cache hit already has everything, so
# skip setup-vp and do its remaining work here. This keeps npm outages from
# failing jobs.
- name: Use cached Vite+
if: steps.vp_cache.outputs.cache-hit == 'true'
shell: bash
run: |
echo "$HOME/.vite-plus/bin" >> "$GITHUB_PATH"
"$HOME/.vite-plus/bin/vp" env use "${{ steps.vp.outputs.node }}"

- name: Setup Vite+
if: steps.vp_cache.outputs.cache-hit != 'true'
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/mobile-showcase-screenshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ env:

jobs:
ios:
name: iPhone 6.9, iPhone 6.5, and iPad 13
name: iPhone, iPhone Duo, and iPad 13
if: inputs.platform == 'all' || inputs.platform == 'ios'
runs-on: blacksmith-12vcpu-macos-26
# Capturing every palette multiplies the device matrix by six, and only the
Expand Down Expand Up @@ -87,6 +87,9 @@ jobs:
brew install cameroncooke/axe/axe
axe --version

- name: Install foldable simulator controls
run: npm install --global agent-device@0.21.25

- name: Capture iOS showcase
run: pnpm screenshots:mobile --platform ios --appearance "${{ inputs.appearance }}" --theme "${{ inputs.theme }}"

Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ For authorized mobile verification, a missing or outdated native client is a bui
## Pull requests

- Never make a PR unless the developer explicitly asks you to do so.
- If `origin` is git.cafe, follow [GitCafe mirror](docs/operations/development.md#gitcafe-mirror-optional): use `cafe` for existing PRs, issues, comments and labels, but push and open PRs on GitHub, where CI runs.
- Conventional commit titles, plain language: `fix(web): new threads no longer spike CPU`.
- Body: the problem in a sentence or two, then how you fixed it. End with the model and harness that did the work.
- UI changes need before/after images. Motion or timing needs a short video.
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/desktop",
"version": "0.1.1",
"version": "0.1.2",
"private": true,
"type": "module",
"main": "dist-electron/boot.cjs",
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/app/DesktopObservability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,19 +366,19 @@ const resolveOtlpEndpoints = Effect.gen(function* () {
otel,
"traces",
{ url: Option.getOrUndefined(environment.otlpTracesUrl), export: signalExport },
persisted.otlpTracesUrl,
{ bootstrapUrl: undefined, settingsUrl: persisted.otlpTracesUrl },
),
metrics: OtelEnvironment.resolveSignalEndpoint(
otel,
"metrics",
{ url: Option.getOrUndefined(environment.otlpMetricsUrl), export: signalExport },
persisted.otlpMetricsUrl,
{ bootstrapUrl: undefined, settingsUrl: persisted.otlpMetricsUrl },
),
logs: OtelEnvironment.resolveSignalEndpoint(
otel,
"logs",
{ url: Option.getOrUndefined(environment.otlpLogsUrl), export: signalExport },
persisted.otlpLogsUrl,
{ bootstrapUrl: undefined, settingsUrl: persisted.otlpLogsUrl },
),
warnings: otel.warnings,
resourceAttributes: otel.resourceAttributes,
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src/wsl/DesktopWslEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,14 +463,14 @@ cd ${shellQuote(linuxServerDir)} && node <<'NODE' >/dev/null 2>&1
// The sentinel must be a package the CLI bundle leaves external. It used to be
// "effect", back when the bundle externalized its runtime deps and the whole
// node_modules tree was unpacked. The bundle now inlines its JS dependencies,
// so "effect" no longer exists on disk and only the native packages do —
// resolving node-pty is what actually validates the unpacked tree.
// so "effect" no longer exists on disk and only the native packages do.
// Resolving node-pty is what actually validates the unpacked tree.
try { require.resolve("node-pty/package.json"); } catch (_e) { process.exit(3); }
const fs = require("node:fs");
const path = require("node:path");
const pkgDir = path.dirname(require.resolve("node-pty/package.json"));
// node-pty 1.x is N-API based, so a single Linux pty.node is ABI-stable across
// Node versions — require() succeeding IS the real compatibility test. Look in
// Node versions, so require() succeeding IS the real compatibility test. Look in
// the same places node-pty's own loader does.
const candidates = [
path.join(pkgDir, "build", "Release", "pty.node"),
Expand Down Expand Up @@ -699,7 +699,7 @@ const ensureNodePtyImpl = Effect.fn("DesktopWslEnvironment.ensureNodePtyImpl")(f
return {
ok: false,
reason:
'WSL server dependencies could not be loaded (for example "node-pty"). The native packages the server needs are not unpacked where the WSL distro\'s Node can read them — this is a packaging problem with this build. Please report it.',
'WSL server dependencies could not be loaded (for example "node-pty"). The native packages the server needs are not unpacked where the WSL distro\'s Node can read them. This is a packaging problem with this build. Please report it.',
fatal: true,
} as const;
}
Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
href={GITHUB_REPOSITORY_URL}
target="_blank"
rel="noopener noreferrer"
aria-label={`Star T3 Code on GitHub — ${MARKETING_STATS.githubStars} stars`}
aria-label={`Star T3 Code on GitHub, ${MARKETING_STATS.githubStars} stars`}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12 2.3l2.9 5.88 6.49.94-4.7 4.58 1.11 6.47L12 17.12l-5.8 3.05 1.11-6.47-4.7-4.58 6.49-.94L12 2.3z"/>
Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/src/pages/download.astro
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const imageProps = {
} satisfies Partial<ComponentProps<typeof Image>>;
---

<Layout title="Download — T3 Code" description="Download T3 Code for macOS, Windows, or Linux.">
<Layout title="Download | T3 Code" description="Download T3 Code for macOS, Windows, or Linux.">
<div class="download-page" id="download-page" data-channel="stable">
<!-- The sky lives behind the header and fades to the page. It only shows on the nightly channel. -->
<header class="download-head">
Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/src/pages/legal.astro
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const documents = [
---

<Layout
title="Legal — T3 Code"
title="Legal | T3 Code"
description="Privacy, terms, and security documents for T3 Code."
pageClass="legal-document-page"
>
Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/src/pages/privacy-policy.astro
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const sections = [
---

<LegalPage
title="Privacy Policy — T3 Code"
title="Privacy Policy | T3 Code"
description="How T3 Tools, Inc. handles information when you use T3 Code, app.t3.codes, and t3.codes."
heading="T3 Code Privacy Policy"
lede="This Privacy Policy describes how T3 Tools, Inc. collects, uses, and shares personal information when you use the T3 Code desktop, mobile, and hosted web applications, visit the t3.codes marketing site, or use the optional T3 Connect service."
Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/src/pages/security-policy.astro
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const sections = [
---

<LegalPage
title="Security Policy — T3 Code"
title="Security Policy | T3 Code"
description="How to report security issues in T3 Code and how T3 Tools approaches product security."
heading="T3 Code Security Policy"
lede="Security is a shared responsibility. This policy explains how to report a vulnerability, how we protect T3 Tools-operated services, and how you can secure the environments and providers you connect to T3 Code."
Expand Down
6 changes: 3 additions & 3 deletions apps/marketing/src/pages/terms-of-service.astro
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const sections = [
---

<LegalPage
title="Terms of Service — T3 Code"
title="Terms of Service | T3 Code"
description="The terms that govern your use of T3 Code, app.t3.codes, T3 Connect, and t3.codes."
heading="T3 Code Terms of Service"
lede="These Terms govern your use of T3 Code, including its desktop, mobile, and hosted applications, the optional T3 Connect service, and the t3.codes website."
Expand Down Expand Up @@ -197,8 +197,8 @@ const sections = [
immediately where needed to prevent harm or comply with law.
</p>
<p>
Provisions that by their nature should survive termination—including ownership, disclaimers,
limitations of liability, indemnification, and dispute terms—will survive.
Provisions that by their nature should survive termination (including ownership, disclaimers,
limitations of liability, indemnification, and dispute terms) will survive.
</p>
</section>

Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ const config: ExpoConfig = {
slug: "t3-code",
platforms: ["ios", "android"],
scheme: variant.scheme,
version: repoEnv.T3CODE_MOBILE_VERSION ?? "2.0.0",
version: repoEnv.T3CODE_MOBILE_VERSION ?? "2.1.0",
runtimeVersion: {
// Development manifests resolve on every launch, so avoid fingerprint's
// expensive native-project calculation there. Preview and production stay
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
},
"dependencies": {
"@clerk/expo": "catalog:",
"@clerk/expo-native-components": "0.2.0",
"@clerk/expo-native-components": "0.4.0",
"@effect/atom-react": "catalog:",
"@expo-google-fonts/dm-sans": "^0.4.2",
"@expo/metro-runtime": "~58.0.14",
Expand Down
16 changes: 10 additions & 6 deletions apps/mobile/src/components/ui/Toast.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MOBILE_SHAPE } from "../../lib/designTokens";
import { useEffect } from "react";
import { AccessibilityInfo, Platform, View } from "react-native";
import { AccessibilityInfo, Platform, View, useWindowDimensions } from "react-native";

import { AppText } from "./AppText";
import { Button } from "./Button";
Expand All @@ -20,6 +20,13 @@ export interface ToastProps {

/** The owning screen places and dismisses feedback; actions are never implicit. */
export function Toast(props: ToastProps) {
const { fontScale } = useWindowDimensions();
// A one-line message keeps its action on the same row. Descriptions and
// accessibility text sizes need the full width, so the action moves below.
const inlineAction = !props.description && fontScale < 1.5;
const action = props.action ? (
<Button label={props.action.label} onPress={props.action.onPress} />
) : null;
useEffect(() => {
if (Platform.OS === "ios") AccessibilityInfo.announceForAccessibility(props.message);
}, [props.message]);
Expand Down Expand Up @@ -47,15 +54,12 @@ export function Toast(props: ToastProps) {
</AppText>
) : null}
</View>
{inlineAction ? action : null}
{props.onDismiss ? (
<IconButton accessibilityLabel="Dismiss message" icon="xmark" onPress={props.onDismiss} />
) : null}
</View>
{props.action ? (
<View className="mt-2 self-end">
<Button label={props.action.label} onPress={props.action.onPress} />
</View>
) : null}
{action && !inlineAction ? <View className="mt-2 self-end">{action}</View> : null}
</GlassSurface>
);
}
128 changes: 128 additions & 0 deletions apps/mobile/src/features/home/ThreadUndoNotice.android.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { useLayoutEffect, useState } from "react";
import { StyleSheet, View } from "react-native";
import { GestureDetector, usePanGesture } from "react-native-gesture-handler";
import Animated, {
cancelAnimation,
Easing,
FadeOut,
ReduceMotion,
runOnJS,
useAnimatedStyle,
useSharedValue,
withTiming,
type EntryAnimationsValues,
} from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Toast } from "../../components/ui/Toast";
import { MOBILE_MOTION } from "../../lib/designTokens";
import {
dismissThreadUndoNotice,
readThreadUndoNotice,
useThreadUndoNotice,
} from "./thread-undo-notice";

const SWIPE_TIMING = { duration: 160, reduceMotion: ReduceMotion.System };
const ENTER_TIMING = {
duration: MOBILE_MOTION.enter,
easing: Easing.out(Easing.cubic),
reduceMotion: ReduceMotion.System,
};
const NOTICE_EXITING = FadeOut.duration(MOBILE_MOTION.exit).reduceMotion(ReduceMotion.System);
const noticeEntering = (values: EntryAnimationsValues) => {
"worklet";
return {
initialValues: { originY: values.targetOriginY + values.targetHeight + 12 },
animations: { originY: withTiming(values.targetOriginY, ENTER_TIMING) },
};
};

export function ThreadUndoNotice(props: { readonly visible: boolean }) {
const notice = useThreadUndoNotice();
// Replacing a notice must also cancel the native gesture tracking the old one.
const [instance, setInstance] = useState({ notice, key: 0 });
if (instance.notice !== notice) setInstance({ notice, key: instance.key + 1 });
if (!props.visible || notice === null) return null;
return <SwipeableUndoNotice key={instance.key} notice={notice} />;
}

function SwipeableUndoNotice(props: {
readonly notice: NonNullable<ReturnType<typeof useThreadUndoNotice>>;
}) {
const { notice } = props;
const insets = useSafeAreaInsets();
const translation = useSharedValue(0);
const width = useSharedValue(0);

useLayoutEffect(
() => () => {
cancelAnimation(translation);
},
[translation],
);

const dismiss = () => {
// A newer action can replace the notice while the swipe is animating out.
if (readThreadUndoNotice() === notice) dismissThreadUndoNotice();
};
const gesture = usePanGesture({
activeOffsetX: [-10, 10],
failOffsetY: [-12, 12],
onUpdate: (event) => {
translation.set(event.translationX);
},
onDeactivate: (event) => {
if (
!event.canceled &&
(Math.abs(event.translationX) > width.value * 0.25 ||
(Math.abs(event.velocityX) > 800 && event.translationX * event.velocityX > 0))
) {
const direction = Math.sign(event.translationX || event.velocityX);
translation.set(
withTiming(direction * (width.value + 24), SWIPE_TIMING, (finished) => {
if (finished) runOnJS(dismiss)();
}),
);
} else {
translation.set(withTiming(0, SWIPE_TIMING));
}
},
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translation.value }],
}));

// Clip the entrance above navigation; the notice intentionally covers the anchored FAB.
return (
<Animated.View
className="absolute inset-x-3 z-[100] overflow-hidden"
style={{ bottom: insets.bottom + 12 }}
pointerEvents="box-none"
exiting={NOTICE_EXITING}
onLayout={(event) => {
width.set(event.nativeEvent.layout.width);
}}
>
<GestureDetector gesture={gesture}>
<Animated.View collapsable={false} entering={noticeEntering} style={animatedStyle}>
{/* Keep the dismiss target separate so TalkBack can also focus Undo. */}
<View
accessible
accessibilityRole="alert"
accessibilityLiveRegion="assertive"
accessibilityLabel={`${notice.action} thread`}
accessibilityActions={[{ name: "dismiss", label: "Dismiss message" }]}
onAccessibilityAction={({ nativeEvent }) => {
if (nativeEvent.actionName === "dismiss") dismiss();
}}
pointerEvents="none"
style={StyleSheet.absoluteFill}
/>
<Toast
message={`${notice.action} thread`}
action={{ label: "Undo", onPress: () => void notice.undo() }}
/>
</Animated.View>
</GestureDetector>
</Animated.View>
);
}
Loading