diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index 7ac299e..8fbc47a 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -14,6 +14,7 @@ import { Route as SearchRouteImport } from './routes/search'
import { Route as NotificationsRouteImport } from './routes/notifications'
import { Route as ModerationRouteImport } from './routes/moderation'
import { Route as FeedsRouteImport } from './routes/feeds'
+import { Route as AboutRouteImport } from './routes/about'
import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout'
import { Route as IndexRouteImport } from './routes/index'
import { Route as CallbackIndexRouteImport } from './routes/callback/index'
@@ -55,6 +56,11 @@ const FeedsRoute = FeedsRouteImport.update({
path: '/feeds',
getParentRoute: () => rootRouteImport,
} as any)
+const AboutRoute = AboutRouteImport.update({
+ id: '/about',
+ path: '/about',
+ getParentRoute: () => rootRouteImport,
+} as any)
const PathlessLayoutRoute = PathlessLayoutRouteImport.update({
id: '/_pathlessLayout',
getParentRoute: () => rootRouteImport,
@@ -138,6 +144,7 @@ const ProfileDidPostRkeyImageIRoute =
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
+ '/about': typeof AboutRoute
'/feeds': typeof FeedsRoute
'/moderation': typeof ModerationRoute
'/notifications': typeof NotificationsRoute
@@ -158,6 +165,7 @@ export interface FileRoutesByFullPath {
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
+ '/about': typeof AboutRoute
'/feeds': typeof FeedsRoute
'/moderation': typeof ModerationRoute
'/notifications': typeof NotificationsRoute
@@ -180,6 +188,7 @@ export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/_pathlessLayout': typeof PathlessLayoutRouteWithChildren
+ '/about': typeof AboutRoute
'/feeds': typeof FeedsRoute
'/moderation': typeof ModerationRoute
'/notifications': typeof NotificationsRoute
@@ -203,6 +212,7 @@ export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
+ | '/about'
| '/feeds'
| '/moderation'
| '/notifications'
@@ -223,6 +233,7 @@ export interface FileRouteTypes {
fileRoutesByTo: FileRoutesByTo
to:
| '/'
+ | '/about'
| '/feeds'
| '/moderation'
| '/notifications'
@@ -244,6 +255,7 @@ export interface FileRouteTypes {
| '__root__'
| '/'
| '/_pathlessLayout'
+ | '/about'
| '/feeds'
| '/moderation'
| '/notifications'
@@ -267,6 +279,7 @@ export interface FileRouteTypes {
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
PathlessLayoutRoute: typeof PathlessLayoutRouteWithChildren
+ AboutRoute: typeof AboutRoute
FeedsRoute: typeof FeedsRoute
ModerationRoute: typeof ModerationRoute
NotificationsRoute: typeof NotificationsRoute
@@ -317,6 +330,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof FeedsRouteImport
parentRoute: typeof rootRouteImport
}
+ '/about': {
+ id: '/about'
+ path: '/about'
+ fullPath: '/about'
+ preLoaderRoute: typeof AboutRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/_pathlessLayout': {
id: '/_pathlessLayout'
path: ''
@@ -475,6 +495,7 @@ const ProfileDidPostRkeyRouteWithChildren =
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
PathlessLayoutRoute: PathlessLayoutRouteWithChildren,
+ AboutRoute: AboutRoute,
FeedsRoute: FeedsRoute,
ModerationRoute: ModerationRoute,
NotificationsRoute: NotificationsRoute,
diff --git a/src/routes/about.tsx b/src/routes/about.tsx
new file mode 100644
index 0000000..5e89150
--- /dev/null
+++ b/src/routes/about.tsx
@@ -0,0 +1,241 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+import { FORCED_LABELER_DIDS, HOST_ABOUT_MARKDOWN, HOST_ADMIN, HOST_DESCRIPTION, HOST_HERO, HOST_LABELMERGE, HOST_SIGNUP_PDS } from '~/../policy';
+import { Header } from '~/components/Header';
+import { defaultconstellationURL, defaultImgCDN, defaultLycanURL, defaultslingshotURL, defaultVideoCDN } from '~/utils/atoms';
+
+import { ProfileSmall } from './__root';
+import { NotificationItem } from './notifications';
+//import { SettingHeading } from './settings';
+
+export const Route = createFileRoute('/about')({
+ component: RouteComponent,
+})
+
+function RouteComponent() {
+ return (
+
+
{
+ if (window.history.length > 1) {
+ window.history.back();
+ } else {
+ window.location.assign("/");
+ }
+ }}
+ bottomBorderDisabled={false}
+ />
+
+

+
{window.location.host} is a Red Dwarf instance that you can use to participate in the Bluesky social network.
+ {/*

*/}
+
{HOST_DESCRIPTION}
+
+
+
+
+
+ )
+}
+
+const REQUIRED_COMPONENTS = ["PolicyViewer"];
+
+const COMPONENT_MAP: Record = {
+ // todo replace with actual policy viewer
+ PolicyViewer: () => ,
+};
+
+function PolicyViewer() {
+ return (
+ <>
+ {/* TODO: render all of the layered overlay enforced moderation stuff here or something idk.
+ still waiting on the server-sided queryLabels proxy and layered moderation spec and also feature bounded moderation spec to finish */}
+
+ >
+ )
+}
+
+function assertRequiredComponents(input: string) {
+ for (const name of REQUIRED_COMPONENTS) {
+ const pattern = new RegExp(`<${name}\\s*/>`);
+ if (!pattern.test(input)) {
+ throw new Error(
+ `Missing required policy component: <${name} />`
+ );
+ }
+ }
+}
+
+function renderInline(text: string) {
+ const parts: React.ReactNode[] = [];
+ let lastIndex = 0;
+
+ const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
+ let match;
+
+ while ((match = linkRegex.exec(text))) {
+ const [full, label, url] = match;
+ const start = match.index;
+
+ if (start > lastIndex) {
+ parts.push(text.slice(lastIndex, start));
+ }
+
+ parts.push(
+
+ {label}
+
+ );
+
+ lastIndex = start + full.length;
+ }
+
+ if (lastIndex < text.length) {
+ parts.push(text.slice(lastIndex));
+ }
+
+ return parts;
+}
+export function Heading2({title}:{title:string}){
+ return (
+
+ {title}
+
+ )
+}
+export function Heading3({title}:{title:string}){
+ return (
+
+ {title}
+
+ )
+}
+export function Heading4({title}:{title:string}){
+ return (
+
+ {title}
+
+ )
+}
+export function PolicyMarkdown({ source }: { source: string }) {
+ assertRequiredComponents(source);
+
+ const blocks = source
+ .split(/\n{2,}/) // 2+ line breaks = new block
+ .map(b => b.trim())
+ .filter(Boolean);
+
+ return (
+
+ {blocks.map((block, i) => {
+ // Section heading
+ if (block.startsWith("## ")) {
+ const title = block.slice(3).trim();
+ return (
+
+ );
+ }
+
+ // Self-closing component
+ const componentMatch = block.match(/^<([A-Z][A-Za-z0-9_]*)\s*\/>$/);
+ if (componentMatch) {
+ const name = componentMatch[1];
+ const Component = COMPONENT_MAP[name];
+
+ if (!Component) {
+ throw new Error(`Unknown policy component: <${name} />`);
+ }
+
+ return
;
+ }
+
+ // Paragraph
+ return (
+
+ {renderInline(block)}
+
+ );
+ })}
+
+ );
+}
+
+
+function PolicyRenderer(){
+ //
+ // policy.ts vars to show:
+
+ // endorsed feeds (or should it be part of unauthed default experience?)
+ // endorsed feeds (should be shown in the explore tab too in lieu of feed discovery)
+ // - [ ] HOST_UNAUTHED_DEFAULT_FEEDS
+ // endorsed PDS
+ // - [ ] HOST_SIGNUP_PDS
+ // todo move the other default services into policy.ts
+ // todo re- sort policy.ts according to this component
+ // also the default services used like microcosm stuff and lycan and maybe the reliance of an appview for search or some other hting
+
+ // default general host moderation policies
+ // todo: layerd moderataion later pls thanks
+ // show the labelmerge insstance responsible
+ // - [ ] HOST_LABELMERGE
+ // show both the whitelisted source and labeler dids in the same spot.
+ // like on hover / click it opens a dialog / popover to show what authority the labeler has
+ // - [x] FORCED_LABELER_DIDS
+ // - [ ] FORCE_HIDE_LABELS_WHITELISTED_SOURCE
+ // - [ ] FORCE_HIDE_LABELS
+ const hostmandate = FORCED_LABELER_DIDS;
+
+ // unauthed experience
+ // - [ ] UNAUTHED_FORCE_WARN_LABELS
+ // - [ ] UNAUTHED_PREVENT_OPENING_WARNS
+
+
+ return (
+ <>
+ {/* settings heading or about heading? */}
+
+
+ PDS (User Account Storage):
+ {HOST_SIGNUP_PDS || "not set"}
+
+ Labelmerge (Label Cache):
+ {HOST_LABELMERGE || "not set"}
+
+ Constellation (Backlink Index):
+ {defaultconstellationURL || "not set"}
+
+ Slingshot (Record Cache):
+ {defaultslingshotURL || "not set"}
+
+ Image Provider (CDN):
+ {defaultImgCDN || "not set"}
+
+ Video Provider (CDN):
+ {defaultVideoCDN || "not set"}
+
+ Lycan (Personal Search):
+ {defaultLycanURL || "not set"}
+
+ {/* {hostmandate && ()} */}
+
+ {hostmandate && ()}
+ {hostmandate?.map((labeler) => {
+ return (
+ // todo this sucks
+
+ );
+ })}
+
+
+ >
+ )
+}
\ No newline at end of file