···11import { useState, useEffect } from 'react'
22import { createRoot } from 'react-dom/client'
33+import { BrowserRouter, Routes, Route, Link, useNavigate } from 'react-router-dom'
34import { Button } from '@public/components/ui/button'
45import {
56 Tabs,
···2122import { SkeletonShimmer } from '@public/components/ui/skeleton'
2223import { Input } from '@public/components/ui/input'
2324import { RadioGroup, RadioGroupItem } from '@public/components/ui/radio-group'
2525+import { Card } from '@public/components/ui/card'
2426import {
2527 Loader2,
2628 Trash2,
2727- LogOut
2929+ LogOut,
3030+ ArrowLeft,
3131+ Shield,
3232+ AlertCircle,
3333+ CheckCircle,
3434+ Scale
2835} from 'lucide-react'
2936import Layout from '@public/layouts'
3037import { useUserInfo } from './hooks/useUserInfo'
···485492 </a>
486493 </p>
487494 <p className="mt-2">
488488- <a
489489- href="/acceptable-use"
495495+ <Link
496496+ to="/editor/acceptable-use"
490497 className="text-accent hover:text-accent/80 transition-colors font-medium"
491498 >
492499 Acceptable Use Policy
493493- </a>
500500+ </Link>
494501 </p>
495502 </div>
496503 </div>
···853860 )
854861}
855862863863+function AcceptableUsePage() {
864864+ const navigate = useNavigate()
865865+866866+ return (
867867+ <div className="w-full min-h-screen bg-background flex flex-col">
868868+ {/* Header */}
869869+ <header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
870870+ <div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
871871+ <div className="flex items-center gap-2">
872872+ <img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
873873+ <span className="text-xl font-semibold text-foreground">
874874+ wisp.place
875875+ </span>
876876+ </div>
877877+ <Button
878878+ variant="ghost"
879879+ size="sm"
880880+ onClick={() => navigate('/editor')}
881881+ >
882882+ <ArrowLeft className="w-4 h-4 mr-2" />
883883+ Back to Dashboard
884884+ </Button>
885885+ </div>
886886+ </header>
887887+888888+ {/* Hero Section */}
889889+ <div className="bg-gradient-to-b from-accent/10 to-background border-b border-border/40">
890890+ <div className="container mx-auto px-4 py-16 max-w-4xl text-center">
891891+ <div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-accent/20 mb-6">
892892+ <Shield className="w-8 h-8 text-accent" />
893893+ </div>
894894+ <h1 className="text-4xl md:text-5xl font-bold mb-4">Acceptable Use Policy</h1>
895895+ <div className="flex items-center justify-center gap-6 text-sm text-muted-foreground">
896896+ <div className="flex items-center gap-2">
897897+ <span className="font-medium">Effective:</span>
898898+ <span>November 10, 2025</span>
899899+ </div>
900900+ <div className="h-4 w-px bg-border"></div>
901901+ <div className="flex items-center gap-2">
902902+ <span className="font-medium">Last Updated:</span>
903903+ <span>November 10, 2025</span>
904904+ </div>
905905+ </div>
906906+ </div>
907907+ </div>
908908+909909+ {/* Content */}
910910+ <div className="container mx-auto px-4 py-12 max-w-4xl">
911911+ <article className="space-y-12">
912912+ {/* Our Philosophy */}
913913+ <section>
914914+ <h2 className="text-3xl font-bold mb-6 text-foreground">Our Philosophy</h2>
915915+ <div className="space-y-4 text-lg leading-relaxed text-muted-foreground">
916916+ <p>
917917+ wisp.place exists to give you a corner of the internet that's truly yours—a place to create, experiment, and express yourself freely. We believe in the open web and the fundamental importance of free expression. We're not here to police your thoughts, moderate your aesthetics, or judge your taste.
918918+ </p>
919919+ <p>
920920+ That said, we're also real people running real servers in real jurisdictions (the United States and the Netherlands), and there are legal and practical limits to what we can host. This policy aims to be as permissive as possible while keeping the lights on and staying on the right side of the law.
921921+ </p>
922922+ </div>
923923+ </section>
924924+925925+ {/* What You Can Do */}
926926+ <Card className="bg-green-500/5 border-green-500/20 p-8">
927927+ <div className="flex items-start gap-4">
928928+ <div className="flex-shrink-0">
929929+ <CheckCircle className="w-8 h-8 text-green-500" />
930930+ </div>
931931+ <div className="space-y-4">
932932+ <h2 className="text-3xl font-bold text-foreground">What You Can Do</h2>
933933+ <div className="space-y-4 text-lg leading-relaxed text-muted-foreground">
934934+ <p>
935935+ <strong className="text-green-600 dark:text-green-400">Almost anything.</strong> Seriously. Build weird art projects. Write controversial essays. Create spaces that would make corporate platforms nervous. Express unpopular opinions. Make things that are strange, provocative, uncomfortable, or just plain yours.
936936+ </p>
937937+ <p>
938938+ We support creative and personal expression in all its forms, including adult content, political speech, counter-cultural work, and experimental projects.
939939+ </p>
940940+ </div>
941941+ </div>
942942+ </div>
943943+ </Card>
944944+945945+ {/* What You Can't Do */}
946946+ <section>
947947+ <div className="flex items-center gap-3 mb-6">
948948+ <AlertCircle className="w-8 h-8 text-red-500" />
949949+ <h2 className="text-3xl font-bold text-foreground">What You Can't Do</h2>
950950+ </div>
951951+952952+ <div className="space-y-8">
953953+ <Card className="p-6 border-2">
954954+ <h3 className="text-2xl font-semibold mb-4 text-foreground">Illegal Content</h3>
955955+ <p className="text-muted-foreground mb-4">
956956+ Don't host content that's illegal in the United States or the Netherlands. This includes but isn't limited to:
957957+ </p>
958958+ <ul className="space-y-3 text-muted-foreground">
959959+ <li className="flex items-start gap-3">
960960+ <span className="text-red-500 mt-1">•</span>
961961+ <span><strong>Child sexual abuse material (CSAM)</strong> involving real minors in any form</span>
962962+ </li>
963963+ <li className="flex items-start gap-3">
964964+ <span className="text-red-500 mt-1">•</span>
965965+ <span><strong>Realistic or AI-generated depictions</strong> of minors in sexual contexts, including photorealistic renders, deepfakes, or AI-generated imagery</span>
966966+ </li>
967967+ <li className="flex items-start gap-3">
968968+ <span className="text-red-500 mt-1">•</span>
969969+ <span><strong>Non-consensual intimate imagery</strong> (revenge porn, deepfakes, hidden camera footage, etc.)</span>
970970+ </li>
971971+ <li className="flex items-start gap-3">
972972+ <span className="text-red-500 mt-1">•</span>
973973+ <span>Content depicting or facilitating human trafficking, sexual exploitation, or sexual violence</span>
974974+ </li>
975975+ <li className="flex items-start gap-3">
976976+ <span className="text-red-500 mt-1">•</span>
977977+ <span>Instructions for manufacturing explosives, biological weapons, or other instruments designed for mass harm</span>
978978+ </li>
979979+ <li className="flex items-start gap-3">
980980+ <span className="text-red-500 mt-1">•</span>
981981+ <span>Content that facilitates imminent violence or terrorism</span>
982982+ </li>
983983+ <li className="flex items-start gap-3">
984984+ <span className="text-red-500 mt-1">•</span>
985985+ <span>Stolen financial information, credentials, or personal data used for fraud</span>
986986+ </li>
987987+ </ul>
988988+ </Card>
989989+990990+ <Card className="p-6 border-2">
991991+ <h3 className="text-2xl font-semibold mb-4 text-foreground">Intellectual Property Violations</h3>
992992+ <div className="space-y-4 text-muted-foreground">
993993+ <p>
994994+ Don't host content that clearly violates someone else's copyright, trademark, or other intellectual property rights. We're required to respond to valid DMCA takedown notices.
995995+ </p>
996996+ <p>
997997+ We understand that copyright law is complicated and sometimes ridiculous. We're not going to proactively scan your site or nitpick over fair use. But if we receive a legitimate legal complaint, we'll have to act on it.
998998+ </p>
999999+ </div>
10001000+ </Card>
10011001+10021002+ <Card className="p-6 border-2 border-red-500/30 bg-red-500/5">
10031003+ <h3 className="text-2xl font-semibold mb-4 text-foreground">Hate Content</h3>
10041004+ <div className="space-y-4 text-muted-foreground">
10051005+ <p>
10061006+ You can express controversial ideas. You can be offensive. You can make people uncomfortable. But pure hate—content that exists solely to dehumanize, threaten, or incite violence against people based on race, ethnicity, religion, gender, sexual orientation, disability, or similar characteristics—isn't welcome here.
10071007+ </p>
10081008+ <p>
10091009+ There's a difference between "I have deeply unpopular opinions about X" and "People like X should be eliminated." The former is protected expression. The latter isn't.
10101010+ </p>
10111011+ <div className="bg-background/50 border-l-4 border-red-500 p-4 rounded">
10121012+ <p className="font-medium text-foreground">
10131013+ <strong>A note on enforcement:</strong> While we're generally permissive and believe in giving people the benefit of the doubt, hate content is where we draw a hard line. I will be significantly more aggressive in moderating this type of content than anything else on this list. If your site exists primarily to spread hate or recruit people into hateful ideologies, you will be removed swiftly and without extensive appeals. This is non-negotiable.
10141014+ </p>
10151015+ </div>
10161016+ </div>
10171017+ </Card>
10181018+10191019+ <Card className="p-6 border-2">
10201020+ <h3 className="text-2xl font-semibold mb-4 text-foreground">Adult Content Guidelines</h3>
10211021+ <div className="space-y-4 text-muted-foreground">
10221022+ <p>
10231023+ Adult content is allowed. This includes sexually explicit material, erotica, adult artwork, and NSFW creative expression.
10241024+ </p>
10251025+ <p className="font-medium">However:</p>
10261026+ <ul className="space-y-2">
10271027+ <li className="flex items-start gap-3">
10281028+ <span className="text-red-500 mt-1">•</span>
10291029+ <span>No content involving real minors in any sexual context whatsoever</span>
10301030+ </li>
10311031+ <li className="flex items-start gap-3">
10321032+ <span className="text-red-500 mt-1">•</span>
10331033+ <span>No photorealistic, AI-generated, or otherwise realistic depictions of minors in sexual contexts</span>
10341034+ </li>
10351035+ <li className="flex items-start gap-3">
10361036+ <span className="text-green-500 mt-1">•</span>
10371037+ <span>Clearly stylized drawings and written fiction are permitted, provided they remain obviously non-photographic in nature</span>
10381038+ </li>
10391039+ <li className="flex items-start gap-3">
10401040+ <span className="text-red-500 mt-1">•</span>
10411041+ <span>No non-consensual content (revenge porn, voyeurism, etc.)</span>
10421042+ </li>
10431043+ <li className="flex items-start gap-3">
10441044+ <span className="text-red-500 mt-1">•</span>
10451045+ <span>No content depicting illegal sexual acts (bestiality, necrophilia, etc.)</span>
10461046+ </li>
10471047+ <li className="flex items-start gap-3">
10481048+ <span className="text-yellow-500 mt-1">•</span>
10491049+ <span>Adult content should be clearly marked as such if discoverable through public directories or search</span>
10501050+ </li>
10511051+ </ul>
10521052+ </div>
10531053+ </Card>
10541054+10551055+ <Card className="p-6 border-2">
10561056+ <h3 className="text-2xl font-semibold mb-4 text-foreground">Malicious Technical Activity</h3>
10571057+ <p className="text-muted-foreground mb-4">Don't use your site to:</p>
10581058+ <ul className="space-y-2 text-muted-foreground">
10591059+ <li className="flex items-start gap-3">
10601060+ <span className="text-red-500 mt-1">•</span>
10611061+ <span>Distribute malware, viruses, or exploits</span>
10621062+ </li>
10631063+ <li className="flex items-start gap-3">
10641064+ <span className="text-red-500 mt-1">•</span>
10651065+ <span>Conduct phishing or social engineering attacks</span>
10661066+ </li>
10671067+ <li className="flex items-start gap-3">
10681068+ <span className="text-red-500 mt-1">•</span>
10691069+ <span>Launch DDoS attacks or network abuse</span>
10701070+ </li>
10711071+ <li className="flex items-start gap-3">
10721072+ <span className="text-red-500 mt-1">•</span>
10731073+ <span>Mine cryptocurrency without explicit user consent</span>
10741074+ </li>
10751075+ <li className="flex items-start gap-3">
10761076+ <span className="text-red-500 mt-1">•</span>
10771077+ <span>Scrape, spam, or abuse other services</span>
10781078+ </li>
10791079+ </ul>
10801080+ </Card>
10811081+ </div>
10821082+ </section>
10831083+10841084+ {/* Our Approach to Enforcement */}
10851085+ <section>
10861086+ <div className="flex items-center gap-3 mb-6">
10871087+ <Scale className="w-8 h-8 text-accent" />
10881088+ <h2 className="text-3xl font-bold text-foreground">Our Approach to Enforcement</h2>
10891089+ </div>
10901090+ <div className="space-y-6">
10911091+ <div className="space-y-4 text-lg leading-relaxed text-muted-foreground">
10921092+ <p>
10931093+ <strong>We actively monitor for obvious violations.</strong> Not to censor your creativity or police your opinions, but to catch the clear-cut stuff that threatens the service's existence and makes this a worse place for everyone. We're looking for the blatantly illegal, the obviously harmful—the stuff that would get servers seized and communities destroyed.
10941094+ </p>
10951095+ <p>
10961096+ We're not reading your blog posts looking for wrongthink. We're making sure this platform doesn't become a haven for the kind of content that ruins good things.
10971097+ </p>
10981098+ </div>
10991099+11001100+ <Card className="p-6 bg-muted/30">
11011101+ <p className="font-semibold mb-3 text-foreground">We take action when:</p>
11021102+ <ol className="space-y-2 text-muted-foreground">
11031103+ <li className="flex items-start gap-3">
11041104+ <span className="font-bold text-accent">1.</span>
11051105+ <span>We identify content that clearly violates this policy during routine monitoring</span>
11061106+ </li>
11071107+ <li className="flex items-start gap-3">
11081108+ <span className="font-bold text-accent">2.</span>
11091109+ <span>We receive a valid legal complaint (DMCA, court order, etc.)</span>
11101110+ </li>
11111111+ <li className="flex items-start gap-3">
11121112+ <span className="font-bold text-accent">3.</span>
11131113+ <span>Someone reports content that violates this policy and we can verify the violation</span>
11141114+ </li>
11151115+ <li className="flex items-start gap-3">
11161116+ <span className="font-bold text-accent">4.</span>
11171117+ <span>Your site is causing technical problems for the service or other users</span>
11181118+ </li>
11191119+ </ol>
11201120+ </Card>
11211121+11221122+ <Card className="p-6 bg-muted/30">
11231123+ <p className="font-semibold mb-3 text-foreground">When we do need to take action, we'll try to:</p>
11241124+ <ul className="space-y-2 text-muted-foreground">
11251125+ <li className="flex items-start gap-3">
11261126+ <span className="text-accent">•</span>
11271127+ <span>Contact you first when legally and practically possible</span>
11281128+ </li>
11291129+ <li className="flex items-start gap-3">
11301130+ <span className="text-accent">•</span>
11311131+ <span>Be transparent about what's happening and why</span>
11321132+ </li>
11331133+ <li className="flex items-start gap-3">
11341134+ <span className="text-accent">•</span>
11351135+ <span>Give you an opportunity to address the issue if appropriate</span>
11361136+ </li>
11371137+ </ul>
11381138+ </Card>
11391139+11401140+ <p className="text-muted-foreground">
11411141+ For serious or repeated violations, we may suspend or terminate your account.
11421142+ </p>
11431143+ </div>
11441144+ </section>
11451145+11461146+ {/* Regional Compliance */}
11471147+ <Card className="p-6 bg-blue-500/5 border-blue-500/20">
11481148+ <h2 className="text-2xl font-bold mb-4 text-foreground">Regional Compliance</h2>
11491149+ <p className="text-muted-foreground">
11501150+ Our servers are located in the United States and the Netherlands. Content hosted on wisp.place must comply with the laws of both jurisdictions. While we aim to provide broad creative freedom, these legal requirements are non-negotiable.
11511151+ </p>
11521152+ </Card>
11531153+11541154+ {/* Changes to This Policy */}
11551155+ <section>
11561156+ <h2 className="text-2xl font-bold mb-4 text-foreground">Changes to This Policy</h2>
11571157+ <p className="text-muted-foreground">
11581158+ We may update this policy as legal requirements or service realities change. If we make significant changes, we'll notify active users.
11591159+ </p>
11601160+ </section>
11611161+11621162+ {/* Questions or Reports */}
11631163+ <section>
11641164+ <h2 className="text-2xl font-bold mb-4 text-foreground">Questions or Reports</h2>
11651165+ <p className="text-muted-foreground">
11661166+ If you have questions about this policy or need to report a violation, contact us at{' '}
11671167+ <a
11681168+ href="mailto:contact@wisp.place"
11691169+ className="text-accent hover:text-accent/80 transition-colors font-medium"
11701170+ >
11711171+ contact@wisp.place
11721172+ </a>
11731173+ .
11741174+ </p>
11751175+ </section>
11761176+11771177+ {/* Final Message */}
11781178+ <Card className="p-8 bg-accent/10 border-accent/30 border-2">
11791179+ <p className="text-lg leading-relaxed text-foreground">
11801180+ <strong>Remember:</strong> This policy exists to keep the service running and this community healthy, not to limit your creativity. When in doubt, ask yourself: "Is this likely to get real-world authorities knocking on doors or make this place worse for everyone?" If the answer is yes, it probably doesn't belong here. Everything else? Go wild.
11811181+ </p>
11821182+ </Card>
11831183+ </article>
11841184+ </div>
11851185+11861186+ {/* Footer */}
11871187+ <footer className="border-t border-border/40 bg-muted/20 mt-auto">
11881188+ <div className="container mx-auto px-4 py-8">
11891189+ <div className="text-center text-sm text-muted-foreground">
11901190+ <p>
11911191+ Built by{' '}
11921192+ <a
11931193+ href="https://bsky.app/profile/nekomimi.pet"
11941194+ target="_blank"
11951195+ rel="noopener noreferrer"
11961196+ className="text-accent hover:text-accent/80 transition-colors font-medium"
11971197+ >
11981198+ @nekomimi.pet
11991199+ </a>
12001200+ {' • '}
12011201+ Contact:{' '}
12021202+ <a
12031203+ href="mailto:contact@wisp.place"
12041204+ className="text-accent hover:text-accent/80 transition-colors font-medium"
12051205+ >
12061206+ contact@wisp.place
12071207+ </a>
12081208+ {' • '}
12091209+ Legal/DMCA:{' '}
12101210+ <a
12111211+ href="mailto:legal@wisp.place"
12121212+ className="text-accent hover:text-accent/80 transition-colors font-medium"
12131213+ >
12141214+ legal@wisp.place
12151215+ </a>
12161216+ </p>
12171217+ <p className="mt-2">
12181218+ <Link
12191219+ to="/editor"
12201220+ className="text-accent hover:text-accent/80 transition-colors font-medium"
12211221+ >
12221222+ Back to Dashboard
12231223+ </Link>
12241224+ </p>
12251225+ </div>
12261226+ </div>
12271227+ </footer>
12281228+ </div>
12291229+ )
12301230+}
12311231+12321232+function App() {
12331233+ return (
12341234+ <BrowserRouter>
12351235+ <Routes>
12361236+ <Route path="/editor" element={<Dashboard />} />
12371237+ <Route path="/editor/acceptable-use" element={<AcceptableUsePage />} />
12381238+ </Routes>
12391239+ </BrowserRouter>
12401240+ )
12411241+}
12421242+8561243const root = createRoot(document.getElementById('elysia')!)
8571244root.render(
8581245 <Layout className="gap-6">
859859- <Dashboard />
12461246+ <App />
8601247 </Layout>
8611248)
+64-6
apps/main-app/public/landingpage.html
···392392 height: 1rem;
393393 }
394394395395+ /* Light mode terminal */
396396+ @media (prefers-color-scheme: light) {
397397+ .terminal {
398398+ background: #f6f8fa;
399399+ border: 1px solid var(--border-light);
400400+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15);
401401+ }
402402+403403+ .terminal-header {
404404+ background: #eaeef2;
405405+ }
406406+407407+ .terminal-title {
408408+ color: #57606a;
409409+ }
410410+411411+ .terminal-body {
412412+ color: #1f2328;
413413+ }
414414+415415+ .t-prompt { color: #6639ba; }
416416+ .t-cmd { color: #1f2328; }
417417+ .t-muted { color: #656d76; }
418418+ .t-success { color: #1a7f37; }
419419+ .t-cyan { color: #0969da; }
420420+ .t-output { color: #656d76; }
421421+ }
422422+395423 /* Features */
396424 .features {
397425 padding: 6rem 2rem;
···665693 <span class="hero-badge">Deploy static sites in seconds</span>
666694 <h1>Ship fast without complexity</h1>
667695 <p class="hero-desc">
668668- Static hosting for developers who value simplicity. Your data, your PDS, your rules.
696696+ Static hosting for developers who value simplicity. Powered by ATProto!
669697 </p>
670698 <div class="cta-group">
671699 <div class="cta-buttons">
···684712 <div class="steps">
685713 <div class="step">
686714 <div class="step-number">01</div>
687687- <h3>Sign in with Bluesky</h3>
688688- <p>Authenticate with your AT Protocol identity. No new accounts needed.</p>
715715+ <h3>Sign in with your AT</h3>
716716+ <p>Your Bluesky account is one! Don't have an AT?<br><a href="{{ATPROTO_LOGIN_URL}}" target="_blank">Make one here ^.^!</a></p>
689717 </div>
690718 <div class="step">
691719 <div class="step-number">02</div>
···757785 </section>
758786759787 <section class="cli-section">
760760- <div class="cli-inner">
788788+ <div class="cli-inner" style="max-width: 1100px; text-align: left;">
761789 <p class="section-label">For power users</p>
762790 <h2 class="section-title">Command line first</h2>
763791 <p class="cli-desc">Full control from your terminal. Integrates with your existing workflow.</p>
···781809 </div>
782810 </div>
783811784784- <div class="cta-buttons">
785785- <a href="https://docs.wisp.place/cli/" target="_blank" class="cta-secondary">Install CLI</a>
812812+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 3rem; align-items: center; margin-top: 3rem;">
813813+ <div>
814814+ <h3 style="font-size: 1.5rem; font-weight: 600; margin-bottom: 1rem;">Self-host anywhere</h3>
815815+ <p style="color: var(--text-muted); margin-bottom: 1.5rem;">Deploy your own server anywhere in the world. The CLI watches the firehose and automatically pulls updates when you push changes—complete control over your hosting infrastructure.</p>
816816+ <div style="display: flex; align-items: center; gap: 1.5rem;">
817817+ <a href="https://docs.wisp.place/cli/" target="_blank" class="cta-secondary">Install CLI</a>
818818+ <code style="color: var(--text); background: var(--code-bg); padding: 0.5rem 0.75rem; border: 1px solid var(--border-light); border-radius: 4px; font-size: 0.875rem;">npm install -g wispctl</code>
819819+ </div>
820820+ </div>
821821+ <div>
822822+ <div class="terminal">
823823+ <div class="terminal-header">
824824+ <div class="terminal-dots">
825825+ <span class="terminal-dot red"></span>
826826+ <span class="terminal-dot yellow"></span>
827827+ <span class="terminal-dot green"></span>
828828+ </div>
829829+ <span class="terminal-title">terminal</span>
830830+ </div>
831831+ <div class="terminal-body">
832832+ <div class="terminal-line"><span class="t-prompt">$</span> <span class="t-cmd">wispctl serve nekomimi.pet -s myblog</span></div>
833833+ <div class="terminal-spacer"></div>
834834+ <div class="terminal-line"><span class="t-muted">◇</span> Serving myblog from nekomimi.pet</div>
835835+ <div class="terminal-line"><span class="t-success">✓</span> <span class="t-success">Server running at http://localhost:8080</span></div>
836836+ <div class="terminal-line"><span class="t-muted">◇</span> Watching for updates via firehose...</div>
837837+ <div class="terminal-spacer"></div>
838838+ <div class="terminal-line"><span class="t-muted">◇</span> Site update received, re-pulling...</div>
839839+ <div class="terminal-line"><span class="t-muted">◇</span> Files to download: 1, unchanged: 4</div>
840840+ <div class="terminal-line"><span class="t-success">✓</span> <span class="t-success">Site reloaded</span></div>
841841+ </div>
842842+ </div>
843843+ </div>
786844 </div>
787845 </div>
788846 </section>
+85-2
apps/main-app/src/index.ts
···11// Fix for Elysia issue with Bun, (see https://github.com/oven-sh/bun/issues/12161)
22process.getBuiltinModule = require;
3344-import { Elysia } from 'elysia'
44+import { Elysia, t } from 'elysia'
55import type { Context } from 'elysia'
66import { cors } from '@elysiajs/cors'
77import { staticPlugin } from '@elysiajs/static'
···119119 set.headers['X-XSS-Protection'] = '1; mode=block'
120120 set.headers['Permissions-Policy'] = 'geolocation=(), microphone=(), camera=()'
121121 })
122122- .onError(observabilityMiddleware('main-app').onError)
122122+ .onError((context) => {
123123+ // Call observability error handler first
124124+ observabilityMiddleware('main-app').onError(context)
125125+126126+ const { error, set, code } = context as any
127127+128128+ // Determine appropriate status code
129129+ let statusCode = 500
130130+ let errorMessage = 'Internal server error'
131131+132132+ if (error instanceof Error) {
133133+ errorMessage = error.message
134134+135135+ // Map common error patterns to status codes
136136+ if (errorMessage.includes('not found') || errorMessage.includes('Not found')) {
137137+ statusCode = 404
138138+ } else if (errorMessage.includes('Unauthorized') || errorMessage.includes('unauthorized')) {
139139+ statusCode = 401
140140+ } else if (errorMessage.includes('Forbidden') || errorMessage.includes('forbidden')) {
141141+ statusCode = 403
142142+ } else if (errorMessage.includes('Invalid') || errorMessage.includes('required') ||
143143+ errorMessage.includes('validation') || errorMessage.includes('bad request')) {
144144+ statusCode = 400
145145+ } else if ((error as any).status) {
146146+ statusCode = (error as any).status
147147+ }
148148+ }
149149+150150+ // Handle Elysia error codes
151151+ if (code === 'NOT_FOUND') {
152152+ statusCode = 404
153153+ errorMessage = 'Not found'
154154+ } else if (code === 'VALIDATION') {
155155+ statusCode = 400
156156+ errorMessage = error instanceof Error ? error.message : 'Validation error'
157157+ } else if (code === 'PARSE') {
158158+ statusCode = 400
159159+ errorMessage = 'Invalid request format'
160160+ }
161161+162162+ set.status = statusCode
163163+ set.headers['Content-Type'] = 'application/json'
164164+165165+ return {
166166+ success: false,
167167+ error: errorMessage,
168168+ statusCode
169169+ }
170170+ })
123171 .use(csrfProtection())
124172 .get('/', async ({ set }) => {
125173 // Build dynamic login URL for AT Protocol OAuth entryway
···146194 prefix: '/'
147195 })
148196 )
197197+ // Production only: serve built assets from dist
198198+ .use(
199199+ Bun.env.NODE_ENV === 'production'
200200+ ? await staticPlugin({
201201+ assets: './apps/main-app/dist',
202202+ prefix: '/dist'
203203+ })
204204+ : (app) => app
205205+ )
206206+ .use(
207207+ Bun.env.NODE_ENV === 'production'
208208+ ? await staticPlugin({
209209+ assets: './apps/main-app/dist/editor',
210210+ prefix: '/editor'
211211+ })
212212+ : (app) => app
213213+ )
214214+ // Production only: serve built HTML for /editor
215215+ .use(
216216+ Bun.env.NODE_ENV === 'production'
217217+ ? new Elysia()
218218+ .get('/editor', async ({ set }) => {
219219+ set.headers['Content-Type'] = 'text/html; charset=utf-8'
220220+ return await Bun.file('./apps/main-app/dist/editor/index.html').text()
221221+ })
222222+ .get('/editor/*', async ({ set }) => {
223223+ set.headers['Content-Type'] = 'text/html; charset=utf-8'
224224+ return await Bun.file('./apps/main-app/dist/editor/index.html').text()
225225+ })
226226+ : (app) => app
227227+ )
228228+ // Redirect old acceptable-use URL to new SPA route
229229+ .get('/acceptable-use', ({ set }) => {
230230+ set.redirect = '/editor/acceptable-use'
231231+ })
149232 .get('/oauth-client-metadata.json', () => {
150233 return createClientMetadata(config)
151234 })
···11-import { Elysia } from 'elysia'
11+import { Elysia, t } from 'elysia'
22import { requireAuth, type AuthenticatedContext } from '../lib/wisp-auth'
33import { NodeOAuthClient } from '@atproto/oauth-client-node'
44import { Agent } from '@atproto/api'