| author | |
| committer | |
| log | d359789ed4ed99b19a0bc6db3c6f6eba4d2b53ce |
| tree | 373a8b776cfdcff8d7a63a8f41ab594c5f9e2087 |
| parent | 462442667e815abd822b042d53924775460b7bcf |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>15 files changed, 1960 insertions(+), 4 deletions(-)
apps/marketing/src/components/LegalPage.astro created+414| ... | ... | @@ -0,0 +1,414 @@ |
| 1 | --- | |
| 2 | import Layout from "../layouts/Layout.astro"; | |
| 3 | ||
| 4 | interface Props { | |
| 5 | readonly title: string; | |
| 6 | readonly description: string; | |
| 7 | readonly heading: string; | |
| 8 | readonly lede: string; | |
| 9 | readonly effectiveDate: string; | |
| 10 | readonly lastUpdated: string; | |
| 11 | readonly sections: ReadonlyArray<readonly [id: string, label: string]>; | |
| 12 | } | |
| 13 | ||
| 14 | const { title, description, heading, lede, effectiveDate, lastUpdated, sections } = Astro.props; | |
| 15 | --- | |
| 16 | ||
| 17 | <Layout title={title} description={description} pageClass="legal-document-page"> | |
| 18 | <div class="legal-page"> | |
| 19 | <a class="legal-index-link" href="/legal"><span aria-hidden="true">&#8592;</span> All legal documents</a> | |
| 20 | <header class="legal-hero"> | |
| 21 | <p class="eyebrow">T3 Tools, Inc. / Legal</p> | |
| 22 | <h1>{heading}</h1> | |
| 23 | <p class="legal-lede">{lede}</p> | |
| 24 | <dl class="legal-dates" aria-label="Document dates"> | |
| 25 | <div> | |
| 26 | <dt>Effective Date</dt> | |
| 27 | <dd>{effectiveDate}</dd> | |
| 28 | </div> | |
| 29 | <div> | |
| 30 | <dt>Last Updated</dt> | |
| 31 | <dd>{lastUpdated}</dd> | |
| 32 | </div> | |
| 33 | </dl> | |
| 34 | </header> | |
| 35 | ||
| 36 | <div class="legal-rule" aria-hidden="true"></div> | |
| 37 | ||
| 38 | <div class="legal-layout"> | |
| 39 | <aside class="legal-nav" aria-label="On this page"> | |
| 40 | <p class="legal-nav-label">On this page</p> | |
| 41 | <ol> | |
| 42 | {sections.map(([id, label], index) => ( | |
| 43 | <li> | |
| 44 | <a href={`#${id}`} data-legal-nav-link={id}> | |
| 45 | <span>{String(index + 1).padStart(2, "0")}</span> | |
| 46 | {label} | |
| 47 | </a> | |
| 48 | </li> | |
| 49 | ))} | |
| 50 | </ol> | |
| 51 | </aside> | |
| 52 | ||
| 53 | <article class="legal-copy"> | |
| 54 | <slot /> | |
| 55 | </article> | |
| 56 | </div> | |
| 57 | </div> | |
| 58 | </Layout> | |
| 59 | ||
| 60 | <script> | |
| 61 | const navLinks = Array.from( | |
| 62 | document.querySelectorAll<HTMLAnchorElement>("[data-legal-nav-link]"), | |
| 63 | ); | |
| 64 | const sections = navLinks | |
| 65 | .map((link) => { | |
| 66 | const id = link.dataset.legalNavLink; | |
| 67 | const section = id ? document.getElementById(id) : null; | |
| 68 | return section ? { link, section } : null; | |
| 69 | }) | |
| 70 | .filter((entry): entry is { link: HTMLAnchorElement; section: HTMLElement } => entry !== null); | |
| 71 | ||
| 72 | let activeId = ""; | |
| 73 | let frame = 0; | |
| 74 | ||
| 75 | const updateActiveSection = () => { | |
| 76 | frame = 0; | |
| 77 | const marker = Math.min(180, window.innerHeight * 0.28); | |
| 78 | let active = sections[0]; | |
| 79 | ||
| 80 | for (const entry of sections) { | |
| 81 | if (entry.section.getBoundingClientRect().top > marker) break; | |
| 82 | active = entry; | |
| 83 | } | |
| 84 | ||
| 85 | if (window.scrollY + window.innerHeight >= document.documentElement.scrollHeight - 2) { | |
| 86 | active = sections[sections.length - 1]; | |
| 87 | } | |
| 88 | ||
| 89 | if (!active || active.section.id === activeId) return; | |
| 90 | activeId = active.section.id; | |
| 91 | ||
| 92 | for (const { link, section } of sections) { | |
| 93 | if (section.id === activeId) { | |
| 94 | link.setAttribute("aria-current", "location"); | |
| 95 | } else { | |
| 96 | link.removeAttribute("aria-current"); | |
| 97 | } | |
| 98 | } | |
| 99 | }; | |
| 100 | ||
| 101 | const scheduleUpdate = () => { | |
| 102 | if (frame) return; | |
| 103 | frame = window.requestAnimationFrame(updateActiveSection); | |
| 104 | }; | |
| 105 | ||
| 106 | updateActiveSection(); | |
| 107 | window.addEventListener("scroll", scheduleUpdate, { passive: true }); | |
| 108 | window.addEventListener("resize", scheduleUpdate); | |
| 109 | </script> | |
| 110 | ||
| 111 | <style is:global> | |
| 112 | .legal-page { | |
| 113 | width: min(1120px, calc(100% - 64px)); | |
| 114 | margin: 0 auto; | |
| 115 | padding: clamp(72px, 10vw, 132px) 0 112px; | |
| 116 | } | |
| 117 | ||
| 118 | .legal-index-link { | |
| 119 | display: none; | |
| 120 | } | |
| 121 | ||
| 122 | .legal-hero { | |
| 123 | max-width: 820px; | |
| 124 | animation: fade-in 0.8s ease-out both; | |
| 125 | } | |
| 126 | ||
| 127 | .legal-hero h1 { | |
| 128 | margin-top: 18px; | |
| 129 | font-size: clamp(3.4rem, 8vw, 7.4rem); | |
| 130 | font-weight: 500; | |
| 131 | letter-spacing: -0.02em; | |
| 132 | line-height: 0.9; | |
| 133 | } | |
| 134 | ||
| 135 | .legal-lede { | |
| 136 | max-width: 720px; | |
| 137 | margin-top: 34px; | |
| 138 | color: var(--fg-muted); | |
| 139 | font-size: clamp(1.05rem, 1.8vw, 1.3rem); | |
| 140 | line-height: 1.65; | |
| 141 | letter-spacing: -0.012em; | |
| 142 | } | |
| 143 | ||
| 144 | .legal-dates { | |
| 145 | display: flex; | |
| 146 | flex-wrap: wrap; | |
| 147 | gap: 36px; | |
| 148 | margin-top: 38px; | |
| 149 | } | |
| 150 | ||
| 151 | .legal-dates div { | |
| 152 | display: grid; | |
| 153 | gap: 4px; | |
| 154 | } | |
| 155 | ||
| 156 | .legal-dates dt, | |
| 157 | .legal-nav-label, | |
| 158 | .section-number { | |
| 159 | color: var(--fg-dim); | |
| 160 | font-family: var(--font-mono); | |
| 161 | font-size: 0.68rem; | |
| 162 | font-weight: 500; | |
| 163 | letter-spacing: 0.12em; | |
| 164 | text-transform: uppercase; | |
| 165 | } | |
| 166 | ||
| 167 | .legal-dates dd { | |
| 168 | color: var(--fg); | |
| 169 | font-family: var(--font-mono); | |
| 170 | font-size: 0.8rem; | |
| 171 | } | |
| 172 | ||
| 173 | .legal-rule { | |
| 174 | height: 1px; | |
| 175 | margin: 72px 0; | |
| 176 | background: linear-gradient(90deg, var(--border-strong), var(--border) 65%, transparent); | |
| 177 | } | |
| 178 | ||
| 179 | .legal-layout { | |
| 180 | display: grid; | |
| 181 | grid-template-columns: 210px minmax(0, 720px); | |
| 182 | gap: clamp(64px, 10vw, 140px); | |
| 183 | align-items: start; | |
| 184 | } | |
| 185 | ||
| 186 | .legal-nav { | |
| 187 | position: sticky; | |
| 188 | top: 88px; | |
| 189 | max-height: calc(100vh - 112px); | |
| 190 | overflow-y: auto; | |
| 191 | scrollbar-width: thin; | |
| 192 | } | |
| 193 | ||
| 194 | .legal-nav ol { | |
| 195 | display: grid; | |
| 196 | gap: 2px; | |
| 197 | margin-top: 16px; | |
| 198 | padding-bottom: 12px; | |
| 199 | list-style: none; | |
| 200 | } | |
| 201 | ||
| 202 | .legal-nav a { | |
| 203 | display: grid; | |
| 204 | grid-template-columns: 24px 1fr; | |
| 205 | gap: 9px; | |
| 206 | margin-left: -10px; | |
| 207 | padding: 7px 8px 7px 10px; | |
| 208 | border-radius: 6px; | |
| 209 | color: var(--fg-dim); | |
| 210 | font-size: 0.78rem; | |
| 211 | line-height: 1.35; | |
| 212 | transition: | |
| 213 | color 0.16s ease, | |
| 214 | background-color 0.16s ease, | |
| 215 | box-shadow 0.16s ease; | |
| 216 | } | |
| 217 | ||
| 218 | .legal-nav a span { | |
| 219 | color: var(--fg-faint); | |
| 220 | font-family: var(--font-mono); | |
| 221 | font-size: 0.64rem; | |
| 222 | line-height: 1.7; | |
| 223 | } | |
| 224 | ||
| 225 | .legal-nav a:hover { | |
| 226 | color: var(--fg); | |
| 227 | } | |
| 228 | ||
| 229 | .legal-nav a[aria-current="location"] { | |
| 230 | color: var(--fg); | |
| 231 | background: color-mix(in srgb, var(--accent) 8%, transparent); | |
| 232 | box-shadow: inset 2px 0 var(--accent); | |
| 233 | } | |
| 234 | ||
| 235 | .legal-nav a[aria-current="location"] span { | |
| 236 | color: var(--accent); | |
| 237 | } | |
| 238 | ||
| 239 | .legal-copy { | |
| 240 | min-width: 0; | |
| 241 | } | |
| 242 | ||
| 243 | .legal-copy section { | |
| 244 | scroll-margin-top: 104px; | |
| 245 | } | |
| 246 | ||
| 247 | .legal-copy section + section { | |
| 248 | margin-top: 82px; | |
| 249 | padding-top: 82px; | |
| 250 | border-top: 1px solid var(--border); | |
| 251 | } | |
| 252 | ||
| 253 | .section-number { | |
| 254 | color: var(--accent); | |
| 255 | } | |
| 256 | ||
| 257 | .legal-copy h2 { | |
| 258 | margin: 8px 0 28px; | |
| 259 | color: var(--fg); | |
| 260 | font-size: clamp(1.75rem, 3vw, 2.45rem); | |
| 261 | font-weight: 500; | |
| 262 | letter-spacing: -0.04em; | |
| 263 | line-height: 1.1; | |
| 264 | } | |
| 265 | ||
| 266 | .legal-copy h3 { | |
| 267 | margin: 32px 0 10px; | |
| 268 | color: var(--fg); | |
| 269 | font-size: 1rem; | |
| 270 | font-weight: 600; | |
| 271 | letter-spacing: -0.015em; | |
| 272 | } | |
| 273 | ||
| 274 | .legal-copy h4 { | |
| 275 | margin: 24px 0 8px; | |
| 276 | color: var(--fg); | |
| 277 | font-size: 0.94rem; | |
| 278 | font-weight: 600; | |
| 279 | } | |
| 280 | ||
| 281 | .legal-copy p, | |
| 282 | .legal-copy li, | |
| 283 | .legal-copy address { | |
| 284 | color: var(--fg-muted); | |
| 285 | font-size: 0.98rem; | |
| 286 | font-style: normal; | |
| 287 | line-height: 1.78; | |
| 288 | } | |
| 289 | ||
| 290 | .legal-copy p + p { | |
| 291 | margin-top: 18px; | |
| 292 | } | |
| 293 | ||
| 294 | .legal-copy strong { | |
| 295 | color: var(--fg); | |
| 296 | font-weight: 600; | |
| 297 | } | |
| 298 | ||
| 299 | .legal-copy ul, | |
| 300 | .legal-copy ol { | |
| 301 | display: grid; | |
| 302 | gap: 12px; | |
| 303 | margin: 18px 0 20px; | |
| 304 | padding-left: 1.2rem; | |
| 305 | } | |
| 306 | ||
| 307 | .legal-copy li { | |
| 308 | padding-left: 7px; | |
| 309 | } | |
| 310 | ||
| 311 | .legal-copy li::marker { | |
| 312 | color: var(--accent); | |
| 313 | } | |
| 314 | ||
| 315 | .legal-copy .provider-list { | |
| 316 | gap: 28px; | |
| 317 | } | |
| 318 | ||
| 319 | .provider-list a, | |
| 320 | .legal-copy a { | |
| 321 | overflow-wrap: anywhere; | |
| 322 | } | |
| 323 | ||
| 324 | .legal-copy a { | |
| 325 | color: var(--fg); | |
| 326 | text-decoration: underline; | |
| 327 | text-decoration-color: color-mix(in srgb, var(--accent) 55%, transparent); | |
| 328 | text-underline-offset: 4px; | |
| 329 | transition: text-decoration-color 0.16s ease; | |
| 330 | } | |
| 331 | ||
| 332 | .legal-copy a:hover { | |
| 333 | text-decoration-color: var(--accent); | |
| 334 | } | |
| 335 | ||
| 336 | .legal-notice { | |
| 337 | color: var(--fg) !important; | |
| 338 | font-weight: 600; | |
| 339 | } | |
| 340 | ||
| 341 | .legal-copy address { | |
| 342 | margin-top: 20px; | |
| 343 | } | |
| 344 | ||
| 345 | .contact-link { | |
| 346 | display: inline-block; | |
| 347 | margin-top: 18px; | |
| 348 | font-family: var(--font-mono); | |
| 349 | font-size: 0.9rem; | |
| 350 | } | |
| 351 | ||
| 352 | @media (max-width: 820px) { | |
| 353 | .legal-index-link { | |
| 354 | display: inline-flex; | |
| 355 | align-items: center; | |
| 356 | gap: 8px; | |
| 357 | margin-bottom: 38px; | |
| 358 | color: var(--fg-dim); | |
| 359 | font-family: var(--font-mono); | |
| 360 | font-size: 0.72rem; | |
| 361 | letter-spacing: 0.04em; | |
| 362 | text-transform: uppercase; | |
| 363 | } | |
| 364 | ||
| 365 | .legal-page { | |
| 366 | width: min(100% - 40px, 720px); | |
| 367 | padding-top: 56px; | |
| 368 | padding-bottom: 80px; | |
| 369 | } | |
| 370 | ||
| 371 | .legal-hero h1 { | |
| 372 | font-size: clamp(3.1rem, 16vw, 5rem); | |
| 373 | } | |
| 374 | ||
| 375 | .legal-rule { | |
| 376 | margin: 52px 0; | |
| 377 | } | |
| 378 | ||
| 379 | .legal-layout { | |
| 380 | display: block; | |
| 381 | } | |
| 382 | ||
| 383 | .legal-nav { | |
| 384 | display: none; | |
| 385 | } | |
| 386 | ||
| 387 | .legal-copy section + section { | |
| 388 | margin-top: 62px; | |
| 389 | padding-top: 62px; | |
| 390 | } | |
| 391 | } | |
| 392 | ||
| 393 | @media (max-width: 420px) { | |
| 394 | .legal-page { | |
| 395 | width: calc(100% - 32px); | |
| 396 | padding-top: 42px; | |
| 397 | } | |
| 398 | ||
| 399 | .legal-lede { | |
| 400 | margin-top: 26px; | |
| 401 | font-size: 1rem; | |
| 402 | } | |
| 403 | ||
| 404 | .legal-dates { | |
| 405 | gap: 24px; | |
| 406 | } | |
| 407 | ||
| 408 | .legal-copy p, | |
| 409 | .legal-copy li, | |
| 410 | .legal-copy address { | |
| 411 | font-size: 0.94rem; | |
| 412 | } | |
| 413 | } | |
| 414 | </style> |
apps/marketing/src/layouts/Layout.astro+31-2| ... | ... | @@ -4,11 +4,13 @@ import { GITHUB_REPOSITORY_URL, MARKETING_STATS } from "../lib/site"; |
| 4 | 4 | interface Props { |
| 5 | 5 | title?: string; |
| 6 | 6 | description?: string; |
| 7 | pageClass?: string; | |
| 7 | 8 | } |
| 8 | 9 | |
| 9 | 10 | const { |
| 10 | 11 | title = "T3 Code", |
| 11 | 12 | description = "T3 Code — The open-source control plane for coding agents.", |
| 13 | pageClass, | |
| 12 | 14 | } = Astro.props; |
| 13 | 15 | --- |
| 14 | 16 | |
| ... | ... | @@ -30,7 +32,7 @@ const { |
| 30 | 32 | <title>{title}</title> |
| 31 | 33 | </head> |
| 32 | 34 | <body> |
| 33 | <div class="page"> | |
| 35 | <div class:list={["page", pageClass]}> | |
| 34 | 36 | <nav class="nav"> |
| 35 | 37 | <div class="nav-inner"> |
| 36 | 38 | <a href="/" class="nav-brand" aria-label="T3 Code home"> |
| ... | ... | @@ -67,6 +69,9 @@ const { |
| 67 | 69 | <a href={GITHUB_REPOSITORY_URL} target="_blank" rel="noopener noreferrer">GitHub</a> |
| 68 | 70 | <a href="https://discord.gg/jn4EGJjrvv" target="_blank" rel="noopener noreferrer">Discord</a> |
| 69 | 71 | <a href="/download">Download</a> |
| 72 | <a href="/terms-of-service">Terms</a> | |
| 73 | <a href="/privacy-policy">Privacy</a> | |
| 74 | <a href="/security-policy">Security</a> | |
| 70 | 75 | </div> |
| 71 | 76 | </div> |
| 72 | 77 | </footer> |
| ... | ... | @@ -279,7 +284,7 @@ const { |
| 279 | 284 | min-height: 100vh; |
| 280 | 285 | display: flex; |
| 281 | 286 | flex-direction: column; |
| 282 | overflow-x: hidden; | |
| 287 | overflow-x: clip; | |
| 283 | 288 | } |
| 284 | 289 | |
| 285 | 290 | .nav { |
| ... | ... | @@ -402,6 +407,8 @@ const { |
| 402 | 407 | |
| 403 | 408 | .footer-links { |
| 404 | 409 | display: flex; |
| 410 | flex-wrap: wrap; | |
| 411 | justify-content: flex-end; | |
| 405 | 412 | gap: 20px; |
| 406 | 413 | } |
| 407 | 414 | |
| ... | ... | @@ -421,6 +428,28 @@ const { |
| 421 | 428 | padding-left: 20px; |
| 422 | 429 | padding-right: 20px; |
| 423 | 430 | } |
| 431 | ||
| 432 | .legal-document-page .nav { | |
| 433 | display: none; | |
| 434 | } | |
| 435 | ||
| 436 | .footer { | |
| 437 | padding-bottom: max(32px, env(safe-area-inset-bottom)); | |
| 438 | } | |
| 439 | ||
| 440 | .footer-inner { | |
| 441 | align-items: flex-start; | |
| 442 | flex-direction: column; | |
| 443 | gap: 28px; | |
| 444 | } | |
| 445 | ||
| 446 | .footer-links { | |
| 447 | display: grid; | |
| 448 | grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| 449 | width: 100%; | |
| 450 | gap: 14px 20px; | |
| 451 | justify-content: initial; | |
| 452 | } | |
| 424 | 453 | } |
| 425 | 454 | |
| 426 | 455 | @media (max-width: 420px) { |
apps/marketing/src/pages/legal.astro created+172| ... | ... | @@ -0,0 +1,172 @@ |
| 1 | --- | |
| 2 | import Layout from "../layouts/Layout.astro"; | |
| 3 | ||
| 4 | const documents = [ | |
| 5 | { | |
| 6 | number: "01", | |
| 7 | title: "Privacy Policy", | |
| 8 | description: "How T3 Code handles information, connected services, and your privacy choices.", | |
| 9 | href: "/privacy-policy", | |
| 10 | }, | |
| 11 | { | |
| 12 | number: "02", | |
| 13 | title: "Terms of Service", | |
| 14 | description: "The terms that govern your use of T3 Code and T3 Tools-operated services.", | |
| 15 | href: "/terms-of-service", | |
| 16 | }, | |
| 17 | { | |
| 18 | number: "03", | |
| 19 | title: "Security Policy", | |
| 20 | description: "Our security practices, responsible disclosure process, and shared responsibilities.", | |
| 21 | href: "/security-policy", | |
| 22 | }, | |
| 23 | ] as const; | |
| 24 | --- | |
| 25 | ||
| 26 | <Layout | |
| 27 | title="Legal — T3 Code" | |
| 28 | description="Privacy, terms, and security documents for T3 Code." | |
| 29 | pageClass="legal-document-page" | |
| 30 | > | |
| 31 | <div class="legal-index"> | |
| 32 | <header class="legal-index-hero"> | |
| 33 | <p class="eyebrow">T3 Tools, Inc. / Legal</p> | |
| 34 | <h1>Legal</h1> | |
| 35 | <p>Policies and terms for T3 Code, collected in one place.</p> | |
| 36 | </header> | |
| 37 | ||
| 38 | <nav class="document-list" aria-label="Legal documents"> | |
| 39 | {documents.map((document) => ( | |
| 40 | <a href={document.href} class="document-card"> | |
| 41 | <span class="document-number">{document.number}</span> | |
| 42 | <span class="document-copy"> | |
| 43 | <strong>{document.title}</strong> | |
| 44 | <span>{document.description}</span> | |
| 45 | </span> | |
| 46 | <span class="document-arrow" aria-hidden="true">&#8599;</span> | |
| 47 | </a> | |
| 48 | ))} | |
| 49 | </nav> | |
| 50 | </div> | |
| 51 | </Layout> | |
| 52 | ||
| 53 | <style> | |
| 54 | .legal-index { | |
| 55 | width: min(960px, calc(100% - 64px)); | |
| 56 | margin: 0 auto; | |
| 57 | padding: clamp(72px, 10vw, 132px) 0 112px; | |
| 58 | } | |
| 59 | ||
| 60 | .legal-index-hero { | |
| 61 | max-width: 680px; | |
| 62 | animation: fade-in 0.7s ease-out both; | |
| 63 | } | |
| 64 | ||
| 65 | .legal-index h1 { | |
| 66 | margin-top: 18px; | |
| 67 | font-size: clamp(4rem, 10vw, 7.4rem); | |
| 68 | font-weight: 500; | |
| 69 | letter-spacing: -0.04em; | |
| 70 | line-height: 0.9; | |
| 71 | } | |
| 72 | ||
| 73 | .legal-index-hero > p:last-child { | |
| 74 | margin-top: 30px; | |
| 75 | color: var(--fg-muted); | |
| 76 | font-size: clamp(1.05rem, 2vw, 1.3rem); | |
| 77 | line-height: 1.65; | |
| 78 | } | |
| 79 | ||
| 80 | .document-list { | |
| 81 | display: grid; | |
| 82 | margin-top: clamp(64px, 9vw, 96px); | |
| 83 | border-top: 1px solid var(--border); | |
| 84 | } | |
| 85 | ||
| 86 | .document-card { | |
| 87 | display: grid; | |
| 88 | grid-template-columns: 42px minmax(0, 1fr) auto; | |
| 89 | gap: 24px; | |
| 90 | align-items: center; | |
| 91 | padding: 30px 8px; | |
| 92 | border-bottom: 1px solid var(--border); | |
| 93 | transition: background-color 0.18s ease, padding 0.18s ease; | |
| 94 | } | |
| 95 | ||
| 96 | .document-number { | |
| 97 | color: var(--accent); | |
| 98 | font-family: var(--font-mono); | |
| 99 | font-size: 0.7rem; | |
| 100 | letter-spacing: 0.1em; | |
| 101 | } | |
| 102 | ||
| 103 | .document-copy { | |
| 104 | display: grid; | |
| 105 | gap: 7px; | |
| 106 | } | |
| 107 | ||
| 108 | .document-copy strong { | |
| 109 | color: var(--fg); | |
| 110 | font-size: 1.2rem; | |
| 111 | font-weight: 500; | |
| 112 | letter-spacing: -0.02em; | |
| 113 | } | |
| 114 | ||
| 115 | .document-copy > span { | |
| 116 | color: var(--fg-dim); | |
| 117 | font-size: 0.9rem; | |
| 118 | line-height: 1.55; | |
| 119 | } | |
| 120 | ||
| 121 | .document-arrow { | |
| 122 | color: var(--fg-dim); | |
| 123 | font-size: 1.25rem; | |
| 124 | transition: color 0.18s ease, transform 0.18s ease; | |
| 125 | } | |
| 126 | ||
| 127 | @media (hover: hover) and (pointer: fine) { | |
| 128 | .document-card:hover { | |
| 129 | padding-left: 16px; | |
| 130 | padding-right: 16px; | |
| 131 | background: rgba(255, 255, 255, 0.025); | |
| 132 | } | |
| 133 | ||
| 134 | .document-card:hover .document-arrow { | |
| 135 | color: var(--fg); | |
| 136 | transform: translate(2px, -2px); | |
| 137 | } | |
| 138 | } | |
| 139 | ||
| 140 | @media (max-width: 820px) { | |
| 141 | .legal-index { | |
| 142 | width: min(100% - 40px, 720px); | |
| 143 | padding-top: 56px; | |
| 144 | padding-bottom: 80px; | |
| 145 | } | |
| 146 | ||
| 147 | .document-card { | |
| 148 | grid-template-columns: 30px minmax(0, 1fr) auto; | |
| 149 | gap: 14px; | |
| 150 | padding: 24px 4px; | |
| 151 | } | |
| 152 | } | |
| 153 | ||
| 154 | @media (max-width: 420px) { | |
| 155 | .legal-index { | |
| 156 | width: calc(100% - 32px); | |
| 157 | padding-top: 42px; | |
| 158 | } | |
| 159 | ||
| 160 | .legal-index h1 { | |
| 161 | font-size: 4.4rem; | |
| 162 | } | |
| 163 | ||
| 164 | .document-copy strong { | |
| 165 | font-size: 1.08rem; | |
| 166 | } | |
| 167 | ||
| 168 | .document-copy > span { | |
| 169 | font-size: 0.84rem; | |
| 170 | } | |
| 171 | } | |
| 172 | </style> |
apps/marketing/src/pages/privacy-policy.astro created+445| ... | ... | @@ -0,0 +1,445 @@ |
| 1 | --- | |
| 2 | import LegalPage from "../components/LegalPage.astro"; | |
| 3 | ||
| 4 | const sections = [ | |
| 5 | ["introduction", "Introduction"], | |
| 6 | ["information", "Personal information we collect"], | |
| 7 | ["uses", "How we use your personal information"], | |
| 8 | ["sharing", "How we share your personal information"], | |
| 9 | ["choices", "Your choices regarding your personal information"], | |
| 10 | ["security", "Security of your personal information"], | |
| 11 | ["international", "International users"], | |
| 12 | ["children", "Children"], | |
| 13 | ["do-not-track", "Do Not Track"], | |
| 14 | ["updates", "Updates to this Privacy Policy"], | |
| 15 | ["contact", "Contact us"], | |
| 16 | ] as const; | |
| 17 | --- | |
| 18 | ||
| 19 | <LegalPage | |
| 20 | title="Privacy Policy — T3 Code" | |
| 21 | description="How T3 Tools, Inc. handles information when you use T3 Code, app.t3.codes, and t3.codes." | |
| 22 | heading="T3 Code Privacy Policy" | |
| 23 | 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." | |
| 24 | effectiveDate="2026-07-14" | |
| 25 | lastUpdated="2026-07-14" | |
| 26 | sections={sections} | |
| 27 | > | |
| 28 | <section id="introduction"> | |
| 29 | <p class="section-number">01</p> | |
| 30 | <h2>Introduction</h2> | |
| 31 | <p> | |
| 32 | This Privacy Policy (the “Policy”) describes how T3 Tools, Inc. (the “Company,” “T3 | |
| 33 | Tools,” “we,” or “us”) collects, uses, and shares personal information when you use the T3 | |
| 34 | Code desktop and mobile applications, the hosted T3 Code web application located at | |
| 35 | {" "}<a href="https://app.t3.codes">https://app.t3.codes</a>, or the optional T3 Connect | |
| 36 | service (together, the “Services”), or visit the T3 Code marketing website located at | |
| 37 | {" "}<a href="https://t3.codes">https://t3.codes</a> (the “Site”). This Policy applies to | |
| 38 | personal information we collect through the Site and the Services, as well as personal | |
| 39 | information you provide to us directly. | |
| 40 | </p> | |
| 41 | <p> | |
| 42 | By using the Site or the Services, you acknowledge the practices described in this | |
| 43 | Policy. If you do not agree with this Policy, please do not use the Site or the Services. | |
| 44 | This Policy does not govern third-party coding agents, artificial-intelligence providers, | |
| 45 | identity providers, or environments that you choose to connect to T3 Code; those third | |
| 46 | parties process information under their own terms and privacy policies. | |
| 47 | </p> | |
| 48 | </section> | |
| 49 | ||
| 50 | <section id="information"> | |
| 51 | <p class="section-number">02</p> | |
| 52 | <h2>Personal Information We Collect</h2> | |
| 53 | ||
| 54 | <p> | |
| 55 | We collect personal information about you in different ways depending on how you use | |
| 56 | T3 Code and which optional features you enable. | |
| 57 | </p> | |
| 58 | ||
| 59 | <h3>Personal Information From Users of T3 Code</h3> | |
| 60 | ||
| 61 | <p> | |
| 62 | When you use T3 Code, we may collect the following categories of personal information: | |
| 63 | </p> | |
| 64 | <ul> | |
| 65 | <li> | |
| 66 | <strong>Account information.</strong> If you create or sign in to a T3 account, we | |
| 67 | receive general identifiers and profile information from our authentication provider | |
| 68 | and the identity provider you select. This may include your name, email address, | |
| 69 | profile image, user ID, sign-in method, and authentication credentials or tokens. | |
| 70 | </li> | |
| 71 | <li> | |
| 72 | <strong>Environment and device information.</strong> If you enable T3 Connect, we | |
| 73 | process information needed to connect and secure your environments and devices, | |
| 74 | including identifiers, labels you assign, public keys, connection endpoints, | |
| 75 | credential hashes, connection status, app and operating-system versions, and | |
| 76 | notification preferences. | |
| 77 | </li> | |
| 78 | <li> | |
| 79 | <strong>Agent activity and notifications.</strong> If you enable notifications or Live | |
| 80 | Activities, we process push tokens and limited activity information needed to provide | |
| 81 | those features, such as project and thread titles, model name, status, and activity | |
| 82 | headline. | |
| 83 | </li> | |
| 84 | <li> | |
| 85 | <strong>Information you provide to us.</strong> We collect information that you choose | |
| 86 | to provide when you request support, send feedback, or otherwise communicate with us. | |
| 87 | </li> | |
| 88 | </ul> | |
| 89 | ||
| 90 | <h3>Personal Information From Users of Our Site Generally</h3> | |
| 91 | <p> | |
| 92 | When you visit the Site or use online portions of the Services, we and our service | |
| 93 | providers may automatically log online identifiers and internet-activity information, | |
| 94 | including your Internet Protocol (IP) address, device and operating-system type, browser | |
| 95 | or app type and version, pages viewed, access times, request timestamps, connection | |
| 96 | events, latency, error details, and diagnostic traces. The amount of information we | |
| 97 | collect depends on the Services you use and your device and browser settings. | |
| 98 | </p> | |
| 99 | <p> | |
| 100 | We use operational and diagnostic information to provide, secure, maintain, and | |
| 101 | troubleshoot the Services, not for targeted advertising. | |
| 102 | </p> | |
| 103 | ||
| 104 | <h3>Information We Get From Others</h3> | |
| 105 | <p> | |
| 106 | We may receive personal information from service providers that help us operate the | |
| 107 | Services, such as authentication and identity providers, app stores, device-platform | |
| 108 | providers, and coding-agent providers you choose to use. We may combine this information | |
| 109 | with information we collect through the Site and the Services. | |
| 110 | </p> | |
| 111 | ||
| 112 | <h3>Cookies and Local Storage</h3> | |
| 113 | <p> | |
| 114 | The Site and online portions of the Services may use cookies or similar technologies to | |
| 115 | operate essential features, maintain sessions, remember preferences, and understand | |
| 116 | whether the Services are functioning correctly. T3 Code also stores connection settings, | |
| 117 | credentials, preferences, drafts, and cached thread, file, branch, and server metadata on | |
| 118 | your device. Credentials and session tokens use platform-provided protected storage where | |
| 119 | supported. Information stored only on your device is not collected by T3 Tools unless it | |
| 120 | is transmitted as part of a feature you use. | |
| 121 | </p> | |
| 122 | ||
| 123 | <h3>Device Permissions</h3> | |
| 124 | <ul> | |
| 125 | <li> | |
| 126 | <strong>Camera.</strong> Used when you choose to scan a pairing QR code. Camera frames | |
| 127 | are processed on your device. | |
| 128 | </li> | |
| 129 | <li> | |
| 130 | <strong>Photos and files.</strong> Items you select are accessed only to preview them or | |
| 131 | attach them to a request you initiate. | |
| 132 | </li> | |
| 133 | <li> | |
| 134 | <strong>Notifications.</strong> Used only if you enable agent-status notifications or | |
| 135 | Live Activities. | |
| 136 | </li> | |
| 137 | <li> | |
| 138 | <strong>Local network.</strong> Used to discover or connect to T3 Code environments | |
| 139 | accessible from your machine over the network. | |
| 140 | </li> | |
| 141 | </ul> | |
| 142 | </section> | |
| 143 | ||
| 144 | <section id="uses"> | |
| 145 | <p class="section-number">03</p> | |
| 146 | <h2>How We Use Your Personal Information</h2> | |
| 147 | <p> | |
| 148 | Subject to this Policy and applicable terms governing third-party applications and | |
| 149 | services, we may use personal information for the following purposes: | |
| 150 | </p> | |
| 151 | <ul> | |
| 152 | <li>To establish and verify your identity and authenticate your account;</li> | |
| 153 | <li>To link and protect devices and environments and deliver optional notifications;</li> | |
| 154 | <li>To process and fulfill requests you make through the Services;</li> | |
| 155 | <li>To provide customer service and respond to support requests and feedback;</li> | |
| 156 | <li>To communicate with you about the Services and your relationship with us;</li> | |
| 157 | <li>To monitor reliability, prevent abuse, and investigate security incidents;</li> | |
| 158 | <li>To comply with applicable law and enforce the terms governing the Services; and</li> | |
| 159 | <li> | |
| 160 | To analyze and improve T3 Code using aggregated, de-identified, or other anonymous | |
| 161 | operational information. | |
| 162 | </li> | |
| 163 | </ul> | |
| 164 | ||
| 165 | <h3>Training of AI Models</h3> | |
| 166 | <p> | |
| 167 | We do not use your personal information, source code, prompts, or agent responses to | |
| 168 | train artificial-intelligence models. | |
| 169 | </p> | |
| 170 | ||
| 171 | <h3>Compliance and Protection</h3> | |
| 172 | <p>We may use personal information to:</p> | |
| 173 | <ul> | |
| 174 | <li>Comply with applicable laws, lawful requests, and legal process;</li> | |
| 175 | <li>Protect the rights, privacy, safety, and property of T3 Tools, our users, and others;</li> | |
| 176 | <li>Audit our internal processes for legal, contractual, and policy compliance;</li> | |
| 177 | <li>Enforce the terms and conditions that govern the Site and the Services; and</li> | |
| 178 | <li> | |
| 179 | Prevent, identify, investigate, and deter fraudulent, harmful, unauthorized, | |
| 180 | unethical, or illegal activity, including cyberattacks and identity theft. | |
| 181 | </li> | |
| 182 | </ul> | |
| 183 | <p> | |
| 184 | We may also use personal information for other purposes consistent with this Policy or | |
| 185 | explained to you when we collect the information. | |
| 186 | </p> | |
| 187 | </section> | |
| 188 | ||
| 189 | <section id="sharing"> | |
| 190 | <p class="section-number">04</p> | |
| 191 | <h2>How We Share Your Personal Information</h2> | |
| 192 | <p> | |
| 193 | We may disclose the categories of personal information described above to the following | |
| 194 | categories of third parties: | |
| 195 | </p> | |
| 196 | ||
| 197 | <h3>Providers You Choose</h3> | |
| 198 | <p> | |
| 199 | At your direction, prompts, files, and related content are sent to coding agents and | |
| 200 | artificial-intelligence providers configured in your environment. Because you bring and | |
| 201 | configure your own coding-agent harness and provider, the applicable third parties vary. | |
| 202 | Those providers process information under their own terms and privacy policies. | |
| 203 | </p> | |
| 204 | ||
| 205 | <h3>Third-Party Service Providers</h3> | |
| 206 | <p> | |
| 207 | We use third-party service providers to help provide the Services and operate our | |
| 208 | business. Depending on the features you use, these providers may include: | |
| 209 | </p> | |
| 210 | <ul class="provider-list"> | |
| 211 | <li> | |
| 212 | <strong>Clerk</strong> <em>(Clerk, Inc.)</em>.<br /> | |
| 213 | We use Clerk to provide account authentication.<br /> | |
| 214 | You can view Clerk's privacy policy here:{" "}<a href="https://clerk.com/legal/privacy" | |
| 215 | >https://clerk.com/legal/privacy</a | |
| 216 | >. | |
| 217 | </li> | |
| 218 | <li> | |
| 219 | <strong>Cloudflare</strong> <em>(Cloudflare, Inc.)</em>.<br /> | |
| 220 | We use Cloudflare for networking, T3 Connect infrastructure, and content delivery.<br /> | |
| 221 | You can view Cloudflare's privacy policy here:{" "}<a | |
| 222 | href="https://www.cloudflare.com/policies/privacy/" | |
| 223 | >https://www.cloudflare.com/policies/privacy/</a | |
| 224 | >. | |
| 225 | </li> | |
| 226 | <li> | |
| 227 | <strong>PlanetScale</strong> <em>(PlanetScale, Inc.)</em>.<br /> | |
| 228 | We use PlanetScale to host the database that supports T3 Connect.<br /> | |
| 229 | You can view PlanetScale's privacy policy here:{" "}<a | |
| 230 | href="https://planetscale.com/legal/privacy" | |
| 231 | >https://planetscale.com/legal/privacy</a | |
| 232 | >. | |
| 233 | </li> | |
| 234 | <li> | |
| 235 | <strong>Vercel</strong> <em>(Vercel Inc.)</em>.<br /> | |
| 236 | We use Vercel to host and deploy the T3 Code Site.<br /> | |
| 237 | You can view Vercel's privacy notice here:{" "}<a | |
| 238 | href="https://vercel.com/legal/privacy-notice" | |
| 239 | >https://vercel.com/legal/privacy-notice</a | |
| 240 | >. | |
| 241 | </li> | |
| 242 | <li> | |
| 243 | <strong>Axiom</strong> <em>(Axiom, Inc.)</em>.<br /> | |
| 244 | We use Axiom for operational diagnostics that help us maintain and troubleshoot the | |
| 245 | Services.<br /> | |
| 246 | You can view Axiom's privacy policy here:{" "}<a | |
| 247 | href="https://axiom.co/docs/legal/privacy">https://axiom.co/docs/legal/privacy</a | |
| 248 | >. | |
| 249 | </li> | |
| 250 | <li> | |
| 251 | <strong>Expo</strong> <em>(650 Industries, Inc.)</em>.<br /> | |
| 252 | We use Expo for application updates and related mobile-app services.<br /> | |
| 253 | You can view Expo's privacy policy here:{" "}<a href="https://expo.dev/privacy" | |
| 254 | >https://expo.dev/privacy</a | |
| 255 | >. | |
| 256 | </li> | |
| 257 | <li> | |
| 258 | <strong>Apple</strong> <em>(Apple Inc.)</em>.<br /> | |
| 259 | We use Apple services for app distribution, optional authentication, and platform | |
| 260 | notifications.<br /> | |
| 261 | You can view Apple's privacy policy here:{" "}<a | |
| 262 | href="https://www.apple.com/legal/privacy/">https://www.apple.com/legal/privacy/</a | |
| 263 | >. | |
| 264 | </li> | |
| 265 | <li> | |
| 266 | <strong>Google</strong> <em>(Google LLC)</em>.<br /> | |
| 267 | We use Google services for app distribution and optional authentication.<br /> | |
| 268 | You can view Google's privacy policy here:{" "}<a | |
| 269 | href="https://policies.google.com/privacy">https://policies.google.com/privacy</a | |
| 270 | >. | |
| 271 | </li> | |
| 272 | </ul> | |
| 273 | <p> | |
| 274 | These providers process information for us under their own contractual and privacy | |
| 275 | obligations. | |
| 276 | </p> | |
| 277 | ||
| 278 | <h3>Affiliates and Professional Advisors</h3> | |
| 279 | <p> | |
| 280 | We may share personal information with our affiliates for purposes consistent with this | |
| 281 | Policy and with lawyers, accountants, bankers, and other professional advisors in the | |
| 282 | course of the services they provide to us. | |
| 283 | </p> | |
| 284 | ||
| 285 | <h3>Corporate Restructuring</h3> | |
| 286 | <p> | |
| 287 | We may share some or all personal information in connection with or during negotiation of | |
| 288 | a merger, financing, acquisition, reorganization, bankruptcy, dissolution, sale of | |
| 289 | assets, or similar transaction. If another company acquires T3 Tools or its assets, that | |
| 290 | company may possess the personal information collected by us and assume the rights and | |
| 291 | obligations described in this Policy. | |
| 292 | </p> | |
| 293 | ||
| 294 | <h3>Legal and Safety Disclosures</h3> | |
| 295 | <p> | |
| 296 | We may disclose personal information if we believe in good faith that disclosure is | |
| 297 | necessary to comply with law or legal process; protect or defend the rights, property, or | |
| 298 | safety of T3 Tools, users of the Site or the Services, or others; investigate or prevent | |
| 299 | fraud, abuse, or unlawful activity; or enforce this Policy and the terms governing the | |
| 300 | Services. We may also share personal information at your direction, with your consent, or | |
| 301 | as described to you when the information is collected. | |
| 302 | </p> | |
| 303 | ||
| 304 | <h3>No Sale of Personal Information</h3> | |
| 305 | <p> | |
| 306 | We do not sell your personal information or share it for cross-context behavioral or | |
| 307 | targeted advertising. | |
| 308 | </p> | |
| 309 | ||
| 310 | <h3>Third-Party Websites and Services</h3> | |
| 311 | <p> | |
| 312 | The Site and the Services may contain links to third-party websites or services. When you | |
| 313 | follow a third-party link or connect a third-party service, that third party may collect | |
| 314 | personal information from you. We do not control and are not responsible for the privacy | |
| 315 | practices or content of third parties. | |
| 316 | </p> | |
| 317 | </section> | |
| 318 | ||
| 319 | <section id="choices"> | |
| 320 | <p class="section-number">05</p> | |
| 321 | <h2>Your Choices Regarding Your Personal Information</h2> | |
| 322 | ||
| 323 | <h3>Account and Connection Choices</h3> | |
| 324 | <p> | |
| 325 | You may use local or direct connections without signing in to a T3 account. You can | |
| 326 | disconnect environments, sign out, disable optional notifications and Live Activities, | |
| 327 | and clear local caches from Settings. You can grant or revoke camera, photo, local-network, | |
| 328 | and notification permissions in your device settings. | |
| 329 | </p> | |
| 330 | ||
| 331 | <h3>Cookies and Local Data</h3> | |
| 332 | <p> | |
| 333 | You can use your browser settings to stop accepting or to delete cookies, although some | |
| 334 | portions of the Site or the Services may not function correctly as a result. Information | |
| 335 | stored only on your device remains there until you clear it, remove the relevant | |
| 336 | connection, or uninstall the app. The mobile app provides cache controls under | |
| 337 | {" "}<strong>Settings → App → Client Storage</strong>. | |
| 338 | </p> | |
| 339 | ||
| 340 | <h3>Retention and Deletion</h3> | |
| 341 | <p> | |
| 342 | We retain account, environment-link, device-registration, notification, and security | |
| 343 | information while your account or the relevant feature is active and for as long as | |
| 344 | reasonably necessary to provide the Services, protect them from abuse, comply with law, | |
| 345 | and resolve disputes. Operational diagnostic traces are configured to expire after 30 | |
| 346 | days. Some records may remain longer in backups or where retention is required for | |
| 347 | security, fraud prevention, or legal compliance. | |
| 348 | </p> | |
| 349 | <p> | |
| 350 | To request deletion of your T3 account and associated T3 Connect data, email{" "}<a | |
| 351 | href="mailto:privacy@t3.tools">privacy@t3.tools</a | |
| 352 | >{" "}from the email address associated with your account. We may need to verify your | |
| 353 | identity. Deleting a T3 account does not delete information held by an environment or | |
| 354 | coding-agent provider you control; contact those providers or delete that information | |
| 355 | directly. | |
| 356 | </p> | |
| 357 | ||
| 358 | <h3>Your Privacy Rights</h3> | |
| 359 | <p> | |
| 360 | Depending on where you live, you may have rights to request access to, correction of, | |
| 361 | deletion of, or a portable copy of your personal information, or to restrict or object | |
| 362 | to certain processing. You may also have the right to appeal our response or complain to | |
| 363 | a data-protection authority. We will honor applicable rights after verifying your | |
| 364 | request. | |
| 365 | </p> | |
| 366 | </section> | |
| 367 | ||
| 368 | <section id="security"> | |
| 369 | <p class="section-number">06</p> | |
| 370 | <h2>Security of Your Personal Information</h2> | |
| 371 | <p> | |
| 372 | T3 Tools is committed to protecting the security of your personal information. We use | |
| 373 | administrative, technical, and organizational safeguards designed to protect personal | |
| 374 | information from unauthorized access, use, or disclosure. These safeguards include | |
| 375 | encrypted network transport, scoped and proof-bound access tokens for supported | |
| 376 | connection flows, protected credential storage, and redaction of authorization headers | |
| 377 | from relay diagnostics. | |
| 378 | </p> | |
| 379 | <p> | |
| 380 | No method of transmission over the Internet or method of electronic storage is completely | |
| 381 | secure. While we use reasonable efforts to protect personal information, we cannot | |
| 382 | guarantee its absolute security. You are responsible for securing the environments, | |
| 383 | coding-agent accounts, credentials, and networks that you connect to T3 Code. | |
| 384 | </p> | |
| 385 | </section> | |
| 386 | ||
| 387 | <section id="international"> | |
| 388 | <p class="section-number">07</p> | |
| 389 | <h2>International Users</h2> | |
| 390 | <p> | |
| 391 | The Site and the Services are provided from the United States and are governed by United | |
| 392 | States law. If you use the Site or the Services from outside the United States, your | |
| 393 | personal information may be transferred to and processed in the United States and other | |
| 394 | countries where we or our service providers operate. Where required, we use lawful | |
| 395 | safeguards for international transfers of personal information. | |
| 396 | </p> | |
| 397 | </section> | |
| 398 | ||
| 399 | <section id="children"> | |
| 400 | <p class="section-number">08</p> | |
| 401 | <h2>Children</h2> | |
| 402 | <p> | |
| 403 | The Site and the Services are not intended for children under 13 years of age, and you | |
| 404 | must be at least 13 years old to use them. We do not knowingly collect, use, or disclose | |
| 405 | personal information from children under 13. If you believe a child under 13 has provided | |
| 406 | us with personal information, please contact us so that we can take appropriate action. | |
| 407 | </p> | |
| 408 | </section> | |
| 409 | ||
| 410 | <section id="do-not-track"> | |
| 411 | <p class="section-number">09</p> | |
| 412 | <h2>Do Not Track</h2> | |
| 413 | <p> | |
| 414 | We currently do not respond to browser “Do Not Track” signals. T3 Code does not use | |
| 415 | personal information for cross-context behavioral or targeted advertising. | |
| 416 | </p> | |
| 417 | </section> | |
| 418 | ||
| 419 | <section id="updates"> | |
| 420 | <p class="section-number">10</p> | |
| 421 | <h2>Updates to This Privacy Policy</h2> | |
| 422 | <p> | |
| 423 | We reserve the right to update this Policy from time to time. If we make material changes, | |
| 424 | we will post the revised Policy at this URL, update the “Last Updated” and “Effective Date” | |
| 425 | above, and provide any additional notice required by law. Except as otherwise indicated, | |
| 426 | changes become effective when the revised Policy is posted. | |
| 427 | </p> | |
| 428 | </section> | |
| 429 | ||
| 430 | <section id="contact"> | |
| 431 | <p class="section-number">11</p> | |
| 432 | <h2>Contact Us</h2> | |
| 433 | <p> | |
| 434 | If you have questions about this Policy or wish to make a privacy or deletion request, | |
| 435 | please contact us at: | |
| 436 | </p> | |
| 437 | <address> | |
| 438 | <strong>T3 Tools, Inc.</strong><br /> | |
| 439 | 2261 Market Street #5309<br /> | |
| 440 | San Francisco, CA 94114<br /> | |
| 441 | United States | |
| 442 | </address> | |
| 443 | <a class="contact-link" href="mailto:privacy@t3.tools">privacy@t3.tools</a> | |
| 444 | </section> | |
| 445 | </LegalPage> |
apps/marketing/src/pages/security-policy.astro created+187| ... | ... | @@ -0,0 +1,187 @@ |
| 1 | --- | |
| 2 | import LegalPage from "../components/LegalPage.astro"; | |
| 3 | ||
| 4 | const sections = [ | |
| 5 | ["introduction", "Introduction"], | |
| 6 | ["reporting", "Reporting security issues"], | |
| 7 | ["safe-harbor", "Responsible research and safe harbor"], | |
| 8 | ["practices", "Our security practices"], | |
| 9 | ["responsibilities", "Your responsibilities"], | |
| 10 | ["updates", "Updates to this policy"], | |
| 11 | ["contact", "Contact us"], | |
| 12 | ] as const; | |
| 13 | --- | |
| 14 | ||
| 15 | <LegalPage | |
| 16 | title="Security Policy — T3 Code" | |
| 17 | description="How to report security issues in T3 Code and how T3 Tools approaches product security." | |
| 18 | heading="T3 Code Security Policy" | |
| 19 | 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." | |
| 20 | effectiveDate="2026-07-15" | |
| 21 | lastUpdated="2026-07-15" | |
| 22 | sections={sections} | |
| 23 | > | |
| 24 | <section id="introduction"> | |
| 25 | <p class="section-number">01</p> | |
| 26 | <h2>Introduction</h2> | |
| 27 | <p> | |
| 28 | This Security Policy describes the vulnerability-reporting process and security practices for | |
| 29 | the T3 Code desktop and mobile applications, the hosted application at{" "}<a | |
| 30 | href="https://app.t3.codes">https://app.t3.codes</a | |
| 31 | >, the optional T3 Connect service, and the T3 Code website at{" "}<a href="https://t3.codes" | |
| 32 | >https://t3.codes</a | |
| 33 | >. | |
| 34 | </p> | |
| 35 | <p> | |
| 36 | T3 Code connects software running on your devices and in environments you control with | |
| 37 | coding-agent harnesses and providers you choose. We secure the software and infrastructure | |
| 38 | operated by T3 Tools, but we do not control the security of your devices, environments, | |
| 39 | repositories, networks, credentials, or third-party providers. Please review the shared | |
| 40 | responsibilities below when assessing risk. | |
| 41 | </p> | |
| 42 | </section> | |
| 43 | ||
| 44 | <section id="reporting"> | |
| 45 | <p class="section-number">02</p> | |
| 46 | <h2>Reporting Security Issues</h2> | |
| 47 | <p> | |
| 48 | If you believe you have found a security vulnerability affecting T3 Code or T3 Tools-operated | |
| 49 | infrastructure, email{" "}<a href="mailto:security@ping.gg">security@ping.gg</a>. Please do not | |
| 50 | disclose the issue publicly until we have had a reasonable opportunity to investigate and | |
| 51 | remediate it. | |
| 52 | </p> | |
| 53 | <p>Please include, when available:</p> | |
| 54 | <ul> | |
| 55 | <li>A description of the issue and its potential impact;</li> | |
| 56 | <li>The affected application, version, URL, endpoint, or component;</li> | |
| 57 | <li>Clear reproduction steps or a minimal proof of concept;</li> | |
| 58 | <li>Relevant logs, screenshots, or request and response details with secrets removed; and</li> | |
| 59 | <li>Your preferred contact information and whether you want public credit.</li> | |
| 60 | </ul> | |
| 61 | <p> | |
| 62 | We aim to acknowledge complete reports within one business day, keep reporters informed of | |
| 63 | material progress, and coordinate disclosure after a fix is available. Resolution time varies | |
| 64 | with severity, complexity, and dependencies on third parties. | |
| 65 | </p> | |
| 66 | </section> | |
| 67 | ||
| 68 | <section id="safe-harbor"> | |
| 69 | <p class="section-number">03</p> | |
| 70 | <h2>Responsible Research and Safe Harbor</h2> | |
| 71 | <p> | |
| 72 | We support good-faith security research. If you make a genuine effort to comply with this | |
| 73 | policy, avoid harm, respect privacy, and report findings promptly, we will treat your research | |
| 74 | as authorized and will not initiate legal action against you for accidental, good-faith | |
| 75 | violations of this policy. If a third party initiates legal action concerning compliant | |
| 76 | research, we will make our authorization known where appropriate. | |
| 77 | </p> | |
| 78 | <p>To remain within this safe harbor, you must:</p> | |
| 79 | <ul> | |
| 80 | <li>Test only accounts, data, and systems you own or have explicit permission to test;</li> | |
| 81 | <li>Stop and report immediately if you encounter personal, confidential, or production data;</li> | |
| 82 | <li>Access only the minimum information needed to demonstrate the issue;</li> | |
| 83 | <li>Avoid disrupting availability, degrading performance, or damaging or deleting data;</li> | |
| 84 | <li>Not use social engineering, phishing, physical attacks, or denial-of-service testing;</li> | |
| 85 | <li>Not test third-party services or infrastructure outside T3 Tools’ control; and</li> | |
| 86 | <li>Give us reasonable time to address the issue before public disclosure.</li> | |
| 87 | </ul> | |
| 88 | <p> | |
| 89 | This safe harbor does not authorize violations of law or activity outside the scope of this | |
| 90 | policy. Vulnerability rewards are not guaranteed and, if offered, are determined by T3 Tools | |
| 91 | in its discretion. | |
| 92 | </p> | |
| 93 | </section> | |
| 94 | ||
| 95 | <section id="practices"> | |
| 96 | <p class="section-number">04</p> | |
| 97 | <h2>Our Security Practices</h2> | |
| 98 | <p> | |
| 99 | We use administrative, technical, and organizational safeguards designed for the nature of T3 | |
| 100 | Code and the information processed by T3 Tools-operated services. These practices include: | |
| 101 | </p> | |
| 102 | <ul> | |
| 103 | <li> | |
| 104 | <strong>Data minimization.</strong> T3 Code is designed so coding-session content can remain | |
| 105 | between your clients, your environment, and the harnesses and providers you configure. We | |
| 106 | process information through T3 Tools-operated infrastructure only when needed for a feature | |
| 107 | you enable. | |
| 108 | </li> | |
| 109 | <li> | |
| 110 | <strong>Encrypted transport.</strong> T3 Tools-operated network services use encrypted | |
| 111 | transport. Supported connection flows use scoped credentials and security controls designed | |
| 112 | to reduce unauthorized reuse. | |
| 113 | </li> | |
| 114 | <li> | |
| 115 | <strong>Credential protection.</strong> T3 Code uses platform-provided protected storage for | |
| 116 | credentials and session tokens where supported. We design operational logging to avoid | |
| 117 | collecting secrets that are not needed to operate or troubleshoot the Services. | |
| 118 | </li> | |
| 119 | <li> | |
| 120 | <strong>Access controls.</strong> Access to production systems and operational data is limited | |
| 121 | according to job responsibilities and protected using authentication and authorization | |
| 122 | controls. | |
| 123 | </li> | |
| 124 | <li> | |
| 125 | <strong>Maintenance and monitoring.</strong> We monitor T3 Tools-operated services for | |
| 126 | reliability and security events, review dependencies, and deploy updates and mitigations as | |
| 127 | appropriate. | |
| 128 | </li> | |
| 129 | <li> | |
| 130 | <strong>Established providers.</strong> We use specialized providers for services such as | |
| 131 | authentication, hosting, and application distribution and evaluate the controls relevant to | |
| 132 | their role. | |
| 133 | </li> | |
| 134 | </ul> | |
| 135 | <p> | |
| 136 | No system is completely secure. These practices reduce risk but do not guarantee that the Site | |
| 137 | or Services will be free from vulnerabilities or unauthorized access. | |
| 138 | </p> | |
| 139 | </section> | |
| 140 | ||
| 141 | <section id="responsibilities"> | |
| 142 | <p class="section-number">05</p> | |
| 143 | <h2>Your Responsibilities</h2> | |
| 144 | <p> | |
| 145 | Because you control much of the T3 Code execution path, you play an important role in securing | |
| 146 | it. You are responsible for: | |
| 147 | </p> | |
| 148 | <ul> | |
| 149 | <li>Keeping T3 Code, your operating systems, and connected tools up to date;</li> | |
| 150 | <li>Securing your devices, repositories, environments, networks, and backups;</li> | |
| 151 | <li>Protecting account, provider, repository, and environment credentials;</li> | |
| 152 | <li>Using multi-factor authentication where your identity and coding-agent providers support it;</li> | |
| 153 | <li>Granting providers and integrations only the permissions they need;</li> | |
| 154 | <li>Reviewing commands, source changes, and other agent output before applying or executing it;</li> | |
| 155 | <li>Removing lost or unused devices and revoking credentials you believe may be compromised; and</li> | |
| 156 | <li>Following the security policies of your selected harnesses, providers, and infrastructure.</li> | |
| 157 | </ul> | |
| 158 | <p> | |
| 159 | Do not send passwords, API keys, access tokens, or private keys in a vulnerability report. | |
| 160 | Revoke any secret that may have been exposed before sharing sanitized evidence with us. | |
| 161 | </p> | |
| 162 | </section> | |
| 163 | ||
| 164 | <section id="updates"> | |
| 165 | <p class="section-number">06</p> | |
| 166 | <h2>Updates to This Policy</h2> | |
| 167 | <p> | |
| 168 | We may update this Security Policy as T3 Code, our infrastructure, and security practices | |
| 169 | evolve. We will post revisions at this URL and update the dates above. Material changes to the | |
| 170 | vulnerability-reporting process or safe-harbor terms will apply prospectively. | |
| 171 | </p> | |
| 172 | </section> | |
| 173 | ||
| 174 | <section id="contact"> | |
| 175 | <p class="section-number">07</p> | |
| 176 | <h2>Contact Us</h2> | |
| 177 | <p> | |
| 178 | Report security issues or ask security-related questions at{" "}<a | |
| 179 | href="mailto:security@ping.gg">security@ping.gg</a | |
| 180 | >. | |
| 181 | </p> | |
| 182 | <p> | |
| 183 | For general legal questions, contact{" "}<a href="mailto:legal@t3.tools">legal@t3.tools</a>. | |
| 184 | For privacy requests, contact{" "}<a href="mailto:privacy@t3.tools">privacy@t3.tools</a>. | |
| 185 | </p> | |
| 186 | </section> | |
| 187 | </LegalPage> |
apps/marketing/src/pages/terms-of-service.astro created+396| ... | ... | @@ -0,0 +1,396 @@ |
| 1 | --- | |
| 2 | import LegalPage from "../components/LegalPage.astro"; | |
| 3 | ||
| 4 | const sections = [ | |
| 5 | ["introduction", "Introduction"], | |
| 6 | ["accounts", "Accounts"], | |
| 7 | ["content", "Your content and providers"], | |
| 8 | ["rights", "T3 Code and proprietary rights"], | |
| 9 | ["acceptable-use", "Acceptable use"], | |
| 10 | ["availability", "Availability and changes"], | |
| 11 | ["fees", "Fees"], | |
| 12 | ["privacy", "Privacy"], | |
| 13 | ["termination", "Suspension and termination"], | |
| 14 | ["beta", "Beta services"], | |
| 15 | ["disclaimers", "Disclaimers"], | |
| 16 | ["liability", "Limitation of liability"], | |
| 17 | ["indemnification", "Indemnification"], | |
| 18 | ["copyright", "Copyright complaints"], | |
| 19 | ["third-parties", "Third-party services"], | |
| 20 | ["feedback", "Feedback"], | |
| 21 | ["disputes", "Dispute resolution"], | |
| 22 | ["miscellaneous", "Miscellaneous"], | |
| 23 | ["changes", "Changes to these Terms"], | |
| 24 | ["contact", "Contact us"], | |
| 25 | ] as const; | |
| 26 | --- | |
| 27 | ||
| 28 | <LegalPage | |
| 29 | title="Terms of Service — T3 Code" | |
| 30 | description="The terms that govern your use of T3 Code, app.t3.codes, T3 Connect, and t3.codes." | |
| 31 | heading="T3 Code Terms of Service" | |
| 32 | 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." | |
| 33 | effectiveDate="2026-07-15" | |
| 34 | lastUpdated="2026-07-15" | |
| 35 | sections={sections} | |
| 36 | > | |
| 37 | <section id="introduction"> | |
| 38 | <p class="section-number">01</p> | |
| 39 | <h2>Introduction</h2> | |
| 40 | <p> | |
| 41 | These Terms of Service (the “Terms”) are a binding agreement between you and T3 Tools, Inc. | |
| 42 | (“T3 Tools,” “we,” “us,” or “our”). They govern your access to and use of the T3 Code desktop | |
| 43 | and mobile applications, the hosted T3 Code application at{" "}<a href="https://app.t3.codes" | |
| 44 | >https://app.t3.codes</a | |
| 45 | >, and the optional T3 Connect service (together, the “Services”), as well as the T3 Code | |
| 46 | marketing website at{" "}<a href="https://t3.codes">https://t3.codes</a> (the “Site”). | |
| 47 | </p> | |
| 48 | <p> | |
| 49 | By accessing or using the Site or Services, you agree to these Terms. If you use the Services | |
| 50 | for an organization, you represent that you have authority to bind that organization, and | |
| 51 | “you” includes the organization. If you do not agree to these Terms, do not use the Site or | |
| 52 | Services. | |
| 53 | </p> | |
| 54 | <p> | |
| 55 | You must be at least 13 years old to use the Site or Services. If the law where you live | |
| 56 | requires a greater age to enter into these Terms, you must meet that requirement or have a | |
| 57 | parent or legal guardian agree on your behalf. | |
| 58 | </p> | |
| 59 | </section> | |
| 60 | ||
| 61 | <section id="accounts"> | |
| 62 | <p class="section-number">02</p> | |
| 63 | <h2>Accounts</h2> | |
| 64 | <p> | |
| 65 | You can use some T3 Code features without creating a T3 account. An account may be required | |
| 66 | for account-backed features, including T3 Connect. You agree to provide accurate account | |
| 67 | information, keep your credentials secure, and promptly notify us if you suspect unauthorized | |
| 68 | access. You are responsible for activity performed through your account and connected devices, | |
| 69 | except to the extent caused by our failure to use reasonable security measures. | |
| 70 | </p> | |
| 71 | <p> | |
| 72 | You may not share credentials in a way that compromises the Services, impersonate another | |
| 73 | person, or create accounts through unauthorized automated means. We may require reasonable | |
| 74 | verification before restoring access or acting on an account request. | |
| 75 | </p> | |
| 76 | </section> | |
| 77 | ||
| 78 | <section id="content"> | |
| 79 | <p class="section-number">03</p> | |
| 80 | <h2>Your Content and Providers</h2> | |
| 81 | <p> | |
| 82 | T3 Code can help you send prompts, source files, images, attachments, review comments, and | |
| 83 | instructions to coding-agent harnesses and providers you choose, and display their responses, | |
| 84 | command output, file changes, source code, and diffs (collectively, “User Content”). You retain | |
| 85 | all rights you have in your User Content. | |
| 86 | </p> | |
| 87 | <p> | |
| 88 | You bring and configure your own coding-agent harness and provider. Your User Content is | |
| 89 | ordinarily processed in the environment you control and by the providers you select, not by | |
| 90 | T3 Tools. When you enable a T3 Tools-operated feature that must transmit or process User | |
| 91 | Content, you grant us a limited, non-exclusive license to host, transmit, reproduce, and | |
| 92 | process that content only as reasonably necessary to provide, secure, and troubleshoot that | |
| 93 | feature. This license ends when the content is no longer needed for those purposes, subject to | |
| 94 | reasonable backup, security, and legal-retention requirements. | |
| 95 | </p> | |
| 96 | <p> | |
| 97 | You represent that you have the rights and permissions needed to use and submit User Content | |
| 98 | and to direct its processing by your selected harnesses and providers. You are responsible for | |
| 99 | reviewing those providers’ terms, privacy practices, data controls, and output before using | |
| 100 | them with confidential, proprietary, regulated, or personal information. | |
| 101 | </p> | |
| 102 | </section> | |
| 103 | ||
| 104 | <section id="rights"> | |
| 105 | <p class="section-number">04</p> | |
| 106 | <h2>T3 Code and Proprietary Rights</h2> | |
| 107 | <p> | |
| 108 | T3 Tools and its licensors own the Site, the Services, and their branding, designs, hosted | |
| 109 | infrastructure, documentation, and other materials, excluding User Content and third-party | |
| 110 | materials. These Terms do not transfer ownership of either party’s intellectual property. | |
| 111 | </p> | |
| 112 | <p> | |
| 113 | Portions of T3 Code are available as open-source software. Your use, copying, modification, and | |
| 114 | distribution of that source code are governed by the license included with the applicable | |
| 115 | repository or component. These Terms govern your use of the hosted Services and do not limit | |
| 116 | rights granted to you by an applicable open-source license. | |
| 117 | </p> | |
| 118 | <p> | |
| 119 | Subject to these Terms, we grant you a limited, non-exclusive, non-transferable, revocable right | |
| 120 | to access and use the Services for lawful personal or internal business purposes. You may not | |
| 121 | use our trademarks, service marks, or trade dress without written permission. | |
| 122 | </p> | |
| 123 | </section> | |
| 124 | ||
| 125 | <section id="acceptable-use"> | |
| 126 | <p class="section-number">05</p> | |
| 127 | <h2>Acceptable Use</h2> | |
| 128 | <p>You may not use the Site or Services to:</p> | |
| 129 | <ul> | |
| 130 | <li>Violate applicable law or another person’s rights;</li> | |
| 131 | <li>Upload, transmit, or generate content you do not have the right to use;</li> | |
| 132 | <li>Distribute malware, exploit code, or harmful content except in authorized security work;</li> | |
| 133 | <li>Probe, attack, disrupt, or gain unauthorized access to the Services or another system;</li> | |
| 134 | <li>Bypass rate limits, access controls, security measures, or usage restrictions;</li> | |
| 135 | <li>Interfere with other users or place an unreasonable load on shared infrastructure;</li> | |
| 136 | <li>Use the Services to facilitate fraud, harassment, abuse, or deceptive conduct; or</li> | |
| 137 | <li>Misrepresent that output or activity from a third-party agent was produced or endorsed by us.</li> | |
| 138 | </ul> | |
| 139 | <p> | |
| 140 | Authorized security research must follow our{" "}<a href="/security-policy">Security Policy</a>. | |
| 141 | We may investigate suspected violations and take proportionate action to protect users and the | |
| 142 | Services. | |
| 143 | </p> | |
| 144 | </section> | |
| 145 | ||
| 146 | <section id="availability"> | |
| 147 | <p class="section-number">06</p> | |
| 148 | <h2>Availability and Changes</h2> | |
| 149 | <p> | |
| 150 | We may add, change, suspend, or discontinue features, integrations, or supported platforms. We | |
| 151 | aim to provide reasonable notice when a change materially reduces an account-backed feature, | |
| 152 | but urgent security, legal, or reliability changes may take effect immediately. The Services | |
| 153 | may be unavailable because of maintenance, provider outages, network conditions, or events | |
| 154 | outside our control. | |
| 155 | </p> | |
| 156 | <p> | |
| 157 | T3 Code depends on software, environments, networks, and coding-agent providers that you | |
| 158 | configure or that third parties operate. We do not control their availability, compatibility, | |
| 159 | pricing, or behavior. | |
| 160 | </p> | |
| 161 | </section> | |
| 162 | ||
| 163 | <section id="fees"> | |
| 164 | <p class="section-number">07</p> | |
| 165 | <h2>Fees</h2> | |
| 166 | <p> | |
| 167 | T3 Code is currently offered without a T3 Tools subscription fee unless we clearly state | |
| 168 | otherwise for a feature. You remain responsible for charges from your coding-agent providers, | |
| 169 | cloud infrastructure, network operators, app stores, and other third parties. If we introduce | |
| 170 | a paid feature, we will present its price and applicable payment terms before you purchase it. | |
| 171 | </p> | |
| 172 | </section> | |
| 173 | ||
| 174 | <section id="privacy"> | |
| 175 | <p class="section-number">08</p> | |
| 176 | <h2>Privacy</h2> | |
| 177 | <p> | |
| 178 | Our{" "}<a href="/privacy-policy">Privacy Policy</a> explains how T3 Tools collects, uses, and | |
| 179 | shares personal information when you use the Site and Services. Third-party environments, | |
| 180 | harnesses, identity providers, and coding-agent providers process information under their own | |
| 181 | terms and privacy policies. | |
| 182 | </p> | |
| 183 | </section> | |
| 184 | ||
| 185 | <section id="termination"> | |
| 186 | <p class="section-number">09</p> | |
| 187 | <h2>Suspension and Termination</h2> | |
| 188 | <p> | |
| 189 | You may stop using the Services at any time. You may disconnect environments and devices from | |
| 190 | within T3 Code and may request deletion of an account as described in our Privacy Policy. | |
| 191 | </p> | |
| 192 | <p> | |
| 193 | We may suspend or terminate access to account-backed or hosted features if we reasonably | |
| 194 | believe you materially violated these Terms, created a security or legal risk, failed to pay an | |
| 195 | applicable fee, or used the Services in a way that could harm other users or shared | |
| 196 | infrastructure. When practicable, we will give notice and an opportunity to cure. We may act | |
| 197 | immediately where needed to prevent harm or comply with law. | |
| 198 | </p> | |
| 199 | <p> | |
| 200 | Provisions that by their nature should survive termination—including ownership, disclaimers, | |
| 201 | limitations of liability, indemnification, and dispute terms—will survive. | |
| 202 | </p> | |
| 203 | </section> | |
| 204 | ||
| 205 | <section id="beta"> | |
| 206 | <p class="section-number">10</p> | |
| 207 | <h2>Beta Services</h2> | |
| 208 | <p> | |
| 209 | T3 Code is an early-stage product, and some or all features may be identified as alpha, beta, | |
| 210 | preview, experimental, or pre-release (“Beta Services”). Beta Services may be incomplete, | |
| 211 | change without notice, contain errors, or lose data. Do not rely on Beta Services as the sole | |
| 212 | copy of important work, and review proposed commands and file changes before applying them. | |
| 213 | </p> | |
| 214 | </section> | |
| 215 | ||
| 216 | <section id="disclaimers"> | |
| 217 | <p class="section-number">11</p> | |
| 218 | <h2>Disclaimers</h2> | |
| 219 | <p class="legal-notice"> | |
| 220 | TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SITE AND SERVICES ARE PROVIDED “AS IS” AND “AS | |
| 221 | AVAILABLE,” WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING WARRANTIES OF | |
| 222 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT. | |
| 223 | </p> | |
| 224 | <p> | |
| 225 | Coding agents and artificial-intelligence systems can produce incorrect, insecure, incomplete, | |
| 226 | or harmful output. You are responsible for reviewing output, commands, patches, and other | |
| 227 | actions before relying on or executing them. We do not warrant that the Services or third-party | |
| 228 | output will be uninterrupted, secure, accurate, error-free, or suitable for your purpose. | |
| 229 | </p> | |
| 230 | <p> | |
| 231 | Some jurisdictions do not allow certain warranty exclusions, so some of the foregoing may not | |
| 232 | apply to you. | |
| 233 | </p> | |
| 234 | </section> | |
| 235 | ||
| 236 | <section id="liability"> | |
| 237 | <p class="section-number">12</p> | |
| 238 | <h2>Limitation of Liability</h2> | |
| 239 | <p class="legal-notice"> | |
| 240 | TO THE MAXIMUM EXTENT PERMITTED BY LAW, T3 TOOLS AND ITS AFFILIATES, OFFICERS, EMPLOYEES, | |
| 241 | AGENTS, AND LICENSORS WILL NOT BE LIABLE FOR INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, | |
| 242 | EXEMPLARY, OR PUNITIVE DAMAGES, OR FOR LOST PROFITS, REVENUE, DATA, GOODWILL, OR BUSINESS | |
| 243 | OPPORTUNITIES, ARISING FROM OR RELATED TO THE SITE, SERVICES, OR THESE TERMS. | |
| 244 | </p> | |
| 245 | <p> | |
| 246 | To the maximum extent permitted by law, our aggregate liability for all claims arising from or | |
| 247 | relating to the Site, Services, or these Terms will not exceed the greater of $100 or the amount | |
| 248 | you paid directly to T3 Tools for the Services during the 12 months before the event giving rise | |
| 249 | to the claim. These limitations apply regardless of the legal theory and even if a remedy fails | |
| 250 | of its essential purpose. They do not limit liability that cannot lawfully be limited. | |
| 251 | </p> | |
| 252 | </section> | |
| 253 | ||
| 254 | <section id="indemnification"> | |
| 255 | <p class="section-number">13</p> | |
| 256 | <h2>Indemnification</h2> | |
| 257 | <p> | |
| 258 | To the extent permitted by law, you will defend, indemnify, and hold harmless T3 Tools and its | |
| 259 | affiliates, officers, employees, and agents from third-party claims, damages, losses, and | |
| 260 | reasonable legal fees arising from your User Content, your use of the Site or Services in | |
| 261 | violation of these Terms or law, or your infringement of another person’s rights. We will | |
| 262 | promptly notify you of a covered claim and reasonably cooperate in its defense. You may not | |
| 263 | settle a claim in a way that admits fault by or imposes obligations on us without our written | |
| 264 | consent. | |
| 265 | </p> | |
| 266 | </section> | |
| 267 | ||
| 268 | <section id="copyright"> | |
| 269 | <p class="section-number">14</p> | |
| 270 | <h2>Copyright Complaints</h2> | |
| 271 | <p> | |
| 272 | If you believe material available through a T3 Tools-operated part of the Site or Services | |
| 273 | infringes your copyright, send a notice to{" "}<a href="mailto:legal@t3.tools">legal@t3.tools</a | |
| 274 | >{" "}identifying the copyrighted work, the allegedly infringing material and its location, your | |
| 275 | contact information, a statement of good-faith belief, a statement under penalty of perjury | |
| 276 | that the notice is accurate and you are authorized to act, and your physical or electronic | |
| 277 | signature. | |
| 278 | </p> | |
| 279 | <p> | |
| 280 | T3 Tools does not control content stored only in a user’s environment or held by a user-selected | |
| 281 | provider. Requests concerning that material should be directed to the operator that hosts it. | |
| 282 | </p> | |
| 283 | </section> | |
| 284 | ||
| 285 | <section id="third-parties"> | |
| 286 | <p class="section-number">15</p> | |
| 287 | <h2>Third-Party Services</h2> | |
| 288 | <p> | |
| 289 | The Services may connect to or display content from third-party coding agents, model providers, | |
| 290 | identity providers, repositories, app stores, websites, and infrastructure. Your use of those | |
| 291 | services is governed by their terms. We do not endorse or control third-party services and are | |
| 292 | not responsible for their content, security, availability, output, or data practices. | |
| 293 | </p> | |
| 294 | <p> | |
| 295 | Third-party and open-source software included with T3 Code is governed by its applicable | |
| 296 | license notices. Where those licenses conflict with these Terms for that software, the | |
| 297 | applicable open-source license controls. | |
| 298 | </p> | |
| 299 | </section> | |
| 300 | ||
| 301 | <section id="feedback"> | |
| 302 | <p class="section-number">16</p> | |
| 303 | <h2>Feedback</h2> | |
| 304 | <p> | |
| 305 | If you send us ideas, suggestions, or other feedback about T3 Code, you grant T3 Tools a | |
| 306 | perpetual, irrevocable, worldwide, royalty-free right to use and commercialize that feedback | |
| 307 | without restriction or compensation. This does not transfer ownership of your User Content. | |
| 308 | </p> | |
| 309 | </section> | |
| 310 | ||
| 311 | <section id="disputes"> | |
| 312 | <p class="section-number">17</p> | |
| 313 | <h2>Dispute Resolution</h2> | |
| 314 | <p class="legal-notice"> | |
| 315 | PLEASE READ THIS SECTION CAREFULLY. IT REQUIRES MOST DISPUTES TO BE RESOLVED BY INDIVIDUAL | |
| 316 | BINDING ARBITRATION AND WAIVES JURY TRIALS AND CLASS OR REPRESENTATIVE ACTIONS. | |
| 317 | </p> | |
| 318 | <h3>Informal Resolution</h3> | |
| 319 | <p> | |
| 320 | Before starting arbitration or a court proceeding, the party raising a dispute must send a | |
| 321 | written notice describing the dispute and requested relief. Notices to us must be sent to{" "}<a | |
| 322 | href="mailto:legal@t3.tools">legal@t3.tools</a | |
| 323 | >. The parties will try in good faith to resolve the dispute for at least 30 days. | |
| 324 | </p> | |
| 325 | <h3>Binding Arbitration</h3> | |
| 326 | <p> | |
| 327 | Except for disputes eligible for small-claims court and requests for injunctive relief to | |
| 328 | protect intellectual property or prevent unauthorized access, unresolved disputes will be | |
| 329 | resolved by binding individual arbitration administered by JAMS under its applicable consumer | |
| 330 | or comprehensive rules. The Federal Arbitration Act governs this agreement. Arbitration may | |
| 331 | take place remotely unless the arbitrator determines that an in-person hearing is necessary. | |
| 332 | </p> | |
| 333 | <h3>No Class Actions</h3> | |
| 334 | <p> | |
| 335 | Disputes must be brought only on an individual basis. Neither party may participate in a class, | |
| 336 | collective, consolidated, or representative action or arbitration to the extent permitted by | |
| 337 | law. | |
| 338 | </p> | |
| 339 | <h3>Opt Out</h3> | |
| 340 | <p> | |
| 341 | You may opt out of this arbitration agreement by emailing{" "}<a href="mailto:legal@t3.tools" | |
| 342 | >legal@t3.tools</a | |
| 343 | > within 30 days after you first accept these Terms. Include your name, the email associated | |
| 344 | with your account if any, and a clear statement that you opt out of arbitration. | |
| 345 | </p> | |
| 346 | <p> | |
| 347 | If the arbitration agreement does not apply, exclusive jurisdiction and venue will lie in the | |
| 348 | state and federal courts located in San Francisco County, California, and each party consents | |
| 349 | to those courts. | |
| 350 | </p> | |
| 351 | </section> | |
| 352 | ||
| 353 | <section id="miscellaneous"> | |
| 354 | <p class="section-number">18</p> | |
| 355 | <h2>Miscellaneous</h2> | |
| 356 | <p> | |
| 357 | California law governs these Terms without regard to conflict-of-law principles, except that | |
| 358 | the Federal Arbitration Act governs the arbitration provisions. These Terms and documents | |
| 359 | incorporated by reference are the entire agreement between you and us regarding the Site and | |
| 360 | Services. If a provision is unenforceable, it will be modified to the minimum extent necessary | |
| 361 | or severed, and the remaining provisions will continue in effect. | |
| 362 | </p> | |
| 363 | <p> | |
| 364 | Our failure to enforce a provision is not a waiver. You may not assign these Terms without our | |
| 365 | written consent. We may assign them in connection with a merger, acquisition, reorganization, | |
| 366 | or sale of assets. We are not liable for delays or failures caused by events beyond our | |
| 367 | reasonable control. Headings are for convenience only, and “including” means “including without | |
| 368 | limitation.” | |
| 369 | </p> | |
| 370 | </section> | |
| 371 | ||
| 372 | <section id="changes"> | |
| 373 | <p class="section-number">19</p> | |
| 374 | <h2>Changes to These Terms</h2> | |
| 375 | <p> | |
| 376 | We may update these Terms from time to time. We will post updated Terms at this URL and update | |
| 377 | the dates above. If a change materially affects your rights, we will provide additional notice | |
| 378 | when reasonably practicable or as required by law. Changes apply prospectively from their | |
| 379 | effective date. Your continued use of the Site or Services after that date means you accept the | |
| 380 | updated Terms. | |
| 381 | </p> | |
| 382 | </section> | |
| 383 | ||
| 384 | <section id="contact"> | |
| 385 | <p class="section-number">20</p> | |
| 386 | <h2>Contact Us</h2> | |
| 387 | <p>If you have questions about these Terms, contact us at:</p> | |
| 388 | <address> | |
| 389 | <strong>T3 Tools, Inc.</strong><br /> | |
| 390 | 2261 Market Street #5309<br /> | |
| 391 | San Francisco, CA 94114<br /> | |
| 392 | United States | |
| 393 | </address> | |
| 394 | <a class="contact-link" href="mailto:legal@t3.tools">legal@t3.tools</a> | |
| 395 | </section> | |
| 396 | </LegalPage> |
apps/mobile/package.json+1-1| ... | ... | @@ -5,7 +5,7 @@ |
| 5 | 5 | "main": "index.ts", |
| 6 | 6 | "scripts": { |
| 7 | 7 | "dev": "expo start --clear", |
| 8 | "dev:client": "APP_VARIANT=development expo start --dev-client --scheme t3code-dev --clear --localhost", | |
| 8 | "dev:client": "APP_VARIANT=development expo start --dev-client --scheme t3code-dev --clear --lan", | |
| 9 | 9 | "dev:client:preview": "eas env:exec preview 'EXPO_NO_DOTENV=1 APP_VARIANT=preview expo start --dev-client --scheme t3code-preview --clear --lan'", |
| 10 | 10 | "start": "expo start", |
| 11 | 11 | "start:dev": "APP_VARIANT=development expo start", |
apps/mobile/src/Stack.tsx+22| ... | ... | @@ -44,8 +44,13 @@ import { SettingsAppearanceRouteScreen } from "./features/settings/SettingsAppea |
| 44 | 44 | import { SettingsClientStorageRouteScreen } from "./features/settings/SettingsClientStorageRouteScreen"; |
| 45 | 45 | import { SettingsAuthRouteScreen } from "./features/settings/SettingsAuthRouteScreen"; |
| 46 | 46 | import { SettingsEnvironmentsRouteScreen } from "./features/settings/SettingsEnvironmentsRouteScreen"; |
| 47 | import { SettingsLegalRouteScreen } from "./features/settings/SettingsLegalRouteScreen"; | |
| 47 | 48 | import { SettingsRouteScreen } from "./features/settings/SettingsRouteScreen"; |
| 48 | 49 | import { SettingsWaitlistRouteScreen } from "./features/settings/SettingsWaitlistRouteScreen"; |
| 50 | import { | |
| 51 | SettingsLegalDocumentCloseHeaderButton, | |
| 52 | SettingsLegalDocumentExternalHeaderButton, | |
| 53 | } from "./features/settings/components/SettingsLegalDocumentRouteScreen"; | |
| 49 | 54 | import { useAppShortcuts } from "./features/shortcuts/useAppShortcuts"; |
| 50 | 55 | import { nativeHeaderScrollEdgeEffects } from "./native/StackHeader"; |
| 51 | 56 | import { useThreadOutboxDrain } from "./state/use-thread-outbox-drain"; |
| ... | ... | @@ -106,6 +111,14 @@ const SHEET_SOLID_HEADER_OPTIONS: AppScreenOptions = { |
| 106 | 111 | unstable_navigationItemStyle: undefined, |
| 107 | 112 | }; |
| 108 | 113 | |
| 114 | const LEGAL_DOCUMENT_HEADER_OPTIONS: AppScreenOptions = { | |
| 115 | ...SHEET_SOLID_HEADER_OPTIONS, | |
| 116 | headerBackVisible: false, | |
| 117 | headerLeft: SettingsLegalDocumentCloseHeaderButton, | |
| 118 | headerRight: SettingsLegalDocumentExternalHeaderButton, | |
| 119 | presentation: "fullScreenModal", | |
| 120 | }; | |
| 121 | ||
| 109 | 122 | const SettingsSheetStack = createNativeStackNavigator({ |
| 110 | 123 | initialRouteName: "Settings", |
| 111 | 124 | screenOptions: { |
| ... | ... | @@ -239,6 +252,7 @@ const WORKSPACE_OVERLAY_ROUTES = new Set([ |
| 239 | 252 | "GitConfirm", |
| 240 | 253 | "GitOverview", |
| 241 | 254 | "NewTaskSheet", |
| 255 | "SettingsLegal", | |
| 242 | 256 | "SettingsSheet", |
| 243 | 257 | "ThreadReviewComment", |
| 244 | 258 | ]); |
| ... | ... | @@ -436,6 +450,14 @@ export const RootStack = createNativeStackNavigator({ |
| 436 | 450 | }), |
| 437 | 451 | }, |
| 438 | 452 | }), |
| 453 | SettingsLegal: createNativeStackScreen({ | |
| 454 | screen: SettingsLegalRouteScreen, | |
| 455 | linking: "settings/legal", | |
| 456 | options: { | |
| 457 | ...LEGAL_DOCUMENT_HEADER_OPTIONS, | |
| 458 | title: "Legal", | |
| 459 | }, | |
| 460 | }), | |
| 439 | 461 | ConnectOnboarding: createNativeStackScreen({ |
| 440 | 462 | screen: ConnectOnboardingRouteScreen, |
| 441 | 463 | linking: "connect-onboarding", |
apps/mobile/src/features/settings/SettingsLegalRouteScreen.tsx created+6| ... | ... | @@ -0,0 +1,6 @@ |
| 1 | import { SettingsLegalDocumentRouteScreen } from "./components/SettingsLegalDocumentRouteScreen"; | |
| 2 | import { LEGAL_URL } from "./lib/legal-document-url"; | |
| 3 | ||
| 4 | export function SettingsLegalRouteScreen() { | |
| 5 | return <SettingsLegalDocumentRouteScreen documentName="Legal" documentUrl={LEGAL_URL} />; | |
| 6 | } |
apps/mobile/src/features/settings/SettingsRouteScreen.tsx+1| ... | ... | @@ -537,6 +537,7 @@ function AppSettingsSection() { |
| 537 | 537 | return ( |
| 538 | 538 | <SettingsSection title="App"> |
| 539 | 539 | <SettingsRow icon="internaldrive" label="Client Storage" target="SettingsClientStorage" /> |
| 540 | <SettingsRow icon="doc.text" label="Legal" fullScreenTarget="SettingsLegal" /> | |
| 540 | 541 | <View className="flex-row items-center gap-4 p-4"> |
| 541 | 542 | <SymbolView |
| 542 | 543 | name="info.circle" |
apps/mobile/src/features/settings/components/SettingsLegalDocumentRouteScreen.tsx created+185| ... | ... | @@ -0,0 +1,185 @@ |
| 1 | import { | |
| 2 | type NavigationProp, | |
| 3 | type ParamListBase, | |
| 4 | type RouteProp, | |
| 5 | useNavigation, | |
| 6 | useRoute, | |
| 7 | } from "@react-navigation/native"; | |
| 8 | import { useCallback, useState } from "react"; | |
| 9 | import { ActivityIndicator, Linking, Pressable, View } from "react-native"; | |
| 10 | import { WebView } from "react-native-webview"; | |
| 11 | ||
| 12 | import { AppText as Text } from "../../../components/AppText"; | |
| 13 | import { LoadingStrip } from "../../../components/LoadingStrip"; | |
| 14 | import { SymbolView } from "../../../components/AppSymbol"; | |
| 15 | import { useThemeColor } from "../../../lib/useThemeColor"; | |
| 16 | import { isLegalDocumentUrl, LEGAL_URL } from "../lib/legal-document-url"; | |
| 17 | ||
| 18 | type SettingsLegalRouteParams = { | |
| 19 | SettingsLegal: { readonly externalUrl?: string } | undefined; | |
| 20 | }; | |
| 21 | ||
| 22 | export function SettingsLegalDocumentCloseHeaderButton() { | |
| 23 | const navigation = useNavigation(); | |
| 24 | const iconColor = useThemeColor("--color-icon"); | |
| 25 | ||
| 26 | return ( | |
| 27 | <Pressable | |
| 28 | accessibilityLabel="Close legal document" | |
| 29 | accessibilityRole="button" | |
| 30 | hitSlop={12} | |
| 31 | onPress={() => navigation.goBack()} | |
| 32 | className="p-2 active:opacity-60" | |
| 33 | > | |
| 34 | <SymbolView | |
| 35 | name="xmark" | |
| 36 | size={18} | |
| 37 | tintColor={iconColor} | |
| 38 | type="monochrome" | |
| 39 | weight="semibold" | |
| 40 | /> | |
| 41 | </Pressable> | |
| 42 | ); | |
| 43 | } | |
| 44 | ||
| 45 | export function SettingsLegalDocumentExternalHeaderButton() { | |
| 46 | const iconColor = useThemeColor("--color-icon"); | |
| 47 | const route = useRoute<RouteProp<SettingsLegalRouteParams, "SettingsLegal">>(); | |
| 48 | const externalUrl = | |
| 49 | route.params?.externalUrl && isLegalDocumentUrl(route.params.externalUrl) | |
| 50 | ? route.params.externalUrl | |
| 51 | : LEGAL_URL; | |
| 52 | ||
| 53 | return ( | |
| 54 | <Pressable | |
| 55 | accessibilityLabel="Open legal documents in external browser" | |
| 56 | accessibilityRole="button" | |
| 57 | hitSlop={12} | |
| 58 | onPress={() => void Linking.openURL(externalUrl).catch(() => undefined)} | |
| 59 | className="p-2 active:opacity-60" | |
| 60 | > | |
| 61 | <SymbolView | |
| 62 | name="safari" | |
| 63 | size={19} | |
| 64 | tintColor={iconColor} | |
| 65 | type="monochrome" | |
| 66 | weight="regular" | |
| 67 | /> | |
| 68 | </Pressable> | |
| 69 | ); | |
| 70 | } | |
| 71 | ||
| 72 | interface SettingsLegalDocumentRouteScreenProps { | |
| 73 | readonly documentName: string; | |
| 74 | readonly documentUrl: string; | |
| 75 | } | |
| 76 | ||
| 77 | export function SettingsLegalDocumentRouteScreen({ | |
| 78 | documentName, | |
| 79 | documentUrl, | |
| 80 | }: SettingsLegalDocumentRouteScreenProps) { | |
| 81 | const navigation = useNavigation<NavigationProp<ParamListBase>>(); | |
| 82 | const iconColor = useThemeColor("--color-icon"); | |
| 83 | const [reloadKey, setReloadKey] = useState(0); | |
| 84 | const [loadProgress, setLoadProgress] = useState(0); | |
| 85 | const [loadError, setLoadError] = useState<string | null>(null); | |
| 86 | ||
| 87 | const openExternalUrl = useCallback((url: string) => { | |
| 88 | void Linking.openURL(url).catch(() => undefined); | |
| 89 | }, []); | |
| 90 | if (loadError) { | |
| 91 | return ( | |
| 92 | <View className="flex-1 items-center justify-center gap-4 bg-sheet px-8"> | |
| 93 | <SymbolView | |
| 94 | name="exclamationmark.triangle" | |
| 95 | size={32} | |
| 96 | tintColor={iconColor} | |
| 97 | type="monochrome" | |
| 98 | weight="regular" | |
| 99 | /> | |
| 100 | <View className="items-center gap-2"> | |
| 101 | <Text className="text-center font-t3-bold text-lg text-foreground"> | |
| 102 | Couldn&apos;t load the {documentName.toLowerCase()} | |
| 103 | </Text> | |
| 104 | <Text selectable className="text-center text-sm leading-normal text-foreground-muted"> | |
| 105 | {loadError} | |
| 106 | </Text> | |
| 107 | </View> | |
| 108 | <View className="w-full max-w-[320px] gap-2"> | |
| 109 | <Pressable | |
| 110 | accessibilityRole="button" | |
| 111 | onPress={() => { | |
| 112 | setLoadError(null); | |
| 113 | setReloadKey((value) => value + 1); | |
| 114 | }} | |
| 115 | className="items-center rounded-xl bg-foreground px-4 py-3 active:opacity-80" | |
| 116 | > | |
| 117 | <Text className="font-t3-bold text-base text-sheet">Try Again</Text> | |
| 118 | </Pressable> | |
| 119 | <Pressable | |
| 120 | accessibilityRole="link" | |
| 121 | onPress={() => openExternalUrl(documentUrl)} | |
| 122 | className="items-center rounded-xl px-4 py-3 active:bg-foreground/5" | |
| 123 | > | |
| 124 | <Text className="font-t3-medium text-base text-foreground-muted">Open in Browser</Text> | |
| 125 | </Pressable> | |
| 126 | </View> | |
| 127 | </View> | |
| 128 | ); | |
| 129 | } | |
| 130 | ||
| 131 | return ( | |
| 132 | <View className="relative flex-1 bg-sheet"> | |
| 133 | {loadProgress > 0 && loadProgress < 1 ? <LoadingStrip progress={loadProgress} /> : null} | |
| 134 | <WebView | |
| 135 | key={reloadKey} | |
| 136 | source={{ uri: documentUrl }} | |
| 137 | originWhitelist={["http://*", "https://*", "mailto:*"]} | |
| 138 | cacheEnabled={false} | |
| 139 | cacheMode="LOAD_NO_CACHE" | |
| 140 | allowsBackForwardNavigationGestures | |
| 141 | domStorageEnabled={false} | |
| 142 | incognito | |
| 143 | javaScriptEnabled={false} | |
| 144 | setSupportMultipleWindows={false} | |
| 145 | sharedCookiesEnabled={false} | |
| 146 | thirdPartyCookiesEnabled={false} | |
| 147 | startInLoadingState | |
| 148 | onShouldStartLoadWithRequest={(request) => { | |
| 149 | if (isLegalDocumentUrl(request.url)) return true; | |
| 150 | ||
| 151 | openExternalUrl(request.url); | |
| 152 | return false; | |
| 153 | }} | |
| 154 | onLoadProgress={(event) => { | |
| 155 | setLoadProgress(event.nativeEvent.progress); | |
| 156 | }} | |
| 157 | onLoadStart={() => { | |
| 158 | setLoadProgress(0.05); | |
| 159 | setLoadError(null); | |
| 160 | }} | |
| 161 | onLoadEnd={(event) => { | |
| 162 | if (isLegalDocumentUrl(event.nativeEvent.url)) { | |
| 163 | navigation.setParams({ externalUrl: event.nativeEvent.url }); | |
| 164 | } | |
| 165 | setLoadProgress(0); | |
| 166 | }} | |
| 167 | onError={(event) => { | |
| 168 | setLoadProgress(0); | |
| 169 | setLoadError(event.nativeEvent.description || "The page could not be loaded."); | |
| 170 | }} | |
| 171 | onHttpError={(event) => { | |
| 172 | if (!isLegalDocumentUrl(event.nativeEvent.url)) return; | |
| 173 | setLoadProgress(0); | |
| 174 | setLoadError(`The server returned status ${event.nativeEvent.statusCode}.`); | |
| 175 | }} | |
| 176 | renderLoading={() => ( | |
| 177 | <View className="absolute inset-0 items-center justify-center bg-sheet"> | |
| 178 | <ActivityIndicator /> | |
| 179 | </View> | |
| 180 | )} | |
| 181 | style={{ flex: 1, backgroundColor: "transparent" }} | |
| 182 | /> | |
| 183 | </View> | |
| 184 | ); | |
| 185 | } |
apps/mobile/src/features/settings/components/SettingsRow.tsx+16-1| ... | ... | @@ -6,7 +6,7 @@ import { SymbolView } from "../../../components/AppSymbol"; |
| 6 | 6 | |
| 7 | 7 | import { AppText as Text } from "../../../components/AppText"; |
| 8 | 8 | import { useThemeColor } from "../../../lib/useThemeColor"; |
| 9 | import type { SettingsSheetTarget } from "./settings-sheet-targets"; | |
| 9 | import type { SettingsLegalDocumentTarget, SettingsSheetTarget } from "./settings-sheet-targets"; | |
| 10 | 10 | |
| 11 | 11 | type SymbolName = ComponentProps<typeof SymbolView>["name"]; |
| 12 | 12 | |
| ... | ... | @@ -16,6 +16,7 @@ export function SettingsRow(props: { |
| 16 | 16 | readonly label: string; |
| 17 | 17 | readonly value?: string; |
| 18 | 18 | readonly target?: SettingsSheetTarget; |
| 19 | readonly fullScreenTarget?: SettingsLegalDocumentTarget; | |
| 19 | 20 | readonly onPress?: () => void; |
| 20 | 21 | }) { |
| 21 | 22 | const navigation = useNavigation(); |
| ... | ... | @@ -72,6 +73,20 @@ export function SettingsRow(props: { |
| 72 | 73 | ); |
| 73 | 74 | } |
| 74 | 75 | |
| 76 | const fullScreenTarget = props.fullScreenTarget; | |
| 77 | if (fullScreenTarget) { | |
| 78 | return ( | |
| 79 | <Pressable | |
| 80 | accessibilityLabel={props.label} | |
| 81 | accessibilityRole="button" | |
| 82 | disabled={props.disabled} | |
| 83 | onPress={() => navigation.navigate(fullScreenTarget)} | |
| 84 | > | |
| 85 | {content} | |
| 86 | </Pressable> | |
| 87 | ); | |
| 88 | } | |
| 89 | ||
| 75 | 90 | return ( |
| 76 | 91 | <Pressable accessibilityRole="button" disabled={props.disabled} onPress={props.onPress}> |
| 77 | 92 | {content} |
apps/mobile/src/features/settings/components/settings-sheet-targets.ts+2| ... | ... | @@ -3,3 +3,5 @@ export type SettingsSheetTarget = |
| 3 | 3 | | "SettingsArchive" |
| 4 | 4 | | "SettingsAppearance" |
| 5 | 5 | | "SettingsClientStorage"; |
| 6 | ||
| 7 | export type SettingsLegalDocumentTarget = "SettingsLegal"; |
apps/mobile/src/features/settings/lib/legal-document-url.test.ts created+24| ... | ... | @@ -0,0 +1,24 @@ |
| 1 | import { describe, expect, it } from "vite-plus/test"; | |
| 2 | ||
| 3 | import { isLegalDocumentUrl } from "./legal-document-url"; | |
| 4 | ||
| 5 | describe("isLegalDocumentUrl", () => { | |
| 6 | it.each([ | |
| 7 | "https://t3.codes/legal", | |
| 8 | "https://t3.codes/legal/", | |
| 9 | "https://t3.codes/privacy-policy?source=app", | |
| 10 | "https://t3.codes/terms-of-service#updates", | |
| 11 | "https://t3.codes/security-policy", | |
| 12 | ])("allows a configured legal document: %s", (url) => { | |
| 13 | expect(isLegalDocumentUrl(url)).toBe(true); | |
| 14 | }); | |
| 15 | ||
| 16 | it.each([ | |
| 17 | "https://t3.codes/download", | |
| 18 | "https://example.com/legal", | |
| 19 | "javascript:alert(1)", | |
| 20 | "not-a-url", | |
| 21 | ])("rejects a URL outside the legal-document allowlist: %s", (url) => { | |
| 22 | expect(isLegalDocumentUrl(url)).toBe(false); | |
| 23 | }); | |
| 24 | }); |
apps/mobile/src/features/settings/lib/legal-document-url.ts created+58| ... | ... | @@ -0,0 +1,58 @@ |
| 1 | const DEFAULT_MARKETING_SITE_URL = "https://t3.codes"; | |
| 2 | ||
| 3 | function resolveMarketingSiteUrl(override: string | undefined): URL { | |
| 4 | try { | |
| 5 | const url = new URL(override?.trim() || DEFAULT_MARKETING_SITE_URL); | |
| 6 | if (url.protocol !== "https:" && url.protocol !== "http:") { | |
| 7 | return new URL(DEFAULT_MARKETING_SITE_URL); | |
| 8 | } | |
| 9 | ||
| 10 | url.search = ""; | |
| 11 | url.hash = ""; | |
| 12 | url.pathname = `${url.pathname.replace(/\/+$/, "")}/`; | |
| 13 | return url; | |
| 14 | } catch { | |
| 15 | return new URL(DEFAULT_MARKETING_SITE_URL); | |
| 16 | } | |
| 17 | } | |
| 18 | ||
| 19 | const MARKETING_SITE_URL = resolveMarketingSiteUrl(process.env.EXPO_PUBLIC_MARKETING_SITE_URL); | |
| 20 | ||
| 21 | function marketingSiteDocumentUrl(path: string): string { | |
| 22 | return new URL(path, MARKETING_SITE_URL).toString(); | |
| 23 | } | |
| 24 | ||
| 25 | export const PRIVACY_POLICY_URL = marketingSiteDocumentUrl("privacy-policy"); | |
| 26 | export const SECURITY_POLICY_URL = marketingSiteDocumentUrl("security-policy"); | |
| 27 | export const TERMS_OF_SERVICE_URL = marketingSiteDocumentUrl("terms-of-service"); | |
| 28 | export const LEGAL_URL = marketingSiteDocumentUrl("legal"); | |
| 29 | ||
| 30 | export const ALLOWED_LEGAL_DOCUMENT_URLS = [ | |
| 31 | LEGAL_URL, | |
| 32 | PRIVACY_POLICY_URL, | |
| 33 | TERMS_OF_SERVICE_URL, | |
| 34 | SECURITY_POLICY_URL, | |
| 35 | ] as const; | |
| 36 | ||
| 37 | function webDocumentIdentity(value: string): string | null { | |
| 38 | try { | |
| 39 | const url = new URL(value); | |
| 40 | if (url.protocol !== "https:" && url.protocol !== "http:") return null; | |
| 41 | ||
| 42 | const pathname = url.pathname.replace(/\/+$/, "") || "/"; | |
| 43 | return `${url.origin}${pathname}`; | |
| 44 | } catch { | |
| 45 | return null; | |
| 46 | } | |
| 47 | } | |
| 48 | ||
| 49 | const ALLOWED_LEGAL_DOCUMENT_IDENTITIES = new Set( | |
| 50 | ALLOWED_LEGAL_DOCUMENT_URLS.map(webDocumentIdentity).filter( | |
| 51 | (value): value is string => value !== null, | |
| 52 | ), | |
| 53 | ); | |
| 54 | ||
| 55 | export function isLegalDocumentUrl(value: string): boolean { | |
| 56 | const identity = webDocumentIdentity(value); | |
| 57 | return identity !== null && ALLOWED_LEGAL_DOCUMENT_IDENTITIES.has(identity); | |
| 58 | } |