📚 Enhance project setup & maintenance workflows

- 📝 Add comprehensive README.md, CONTRIBUTING.md, and 🔒 SECURITY.md documentation
- 🔧 Integrate code formatting tool and refactor existing codebase to meet style guidelines
- ⬆️ Update project dependencies to latest stable versions
- 🤖 Implement pre-commit hook for automated code formatting

#documentation #tooling #maintenance
This commit is contained in:
Danny Morabito 2025-03-10 17:14:09 +01:00
parent 25b3972f8b
commit 18e4ad7629
Signed by: dannym
GPG key ID: 7CC8056A5A04557E
47 changed files with 1027 additions and 568 deletions

View file

@ -1,16 +1,16 @@
import { html, css, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import type { RouteParams } from "./router";
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import type { RouteParams } from './router';
import "@components/EveLink";
import '@components/EveLink';
@customElement("arx-404-page")
@customElement('arx-404-page')
export class FourOhFourPage extends LitElement {
@property({ type: Object })
params: RouteParams = {};
@property({ type: String })
path = "";
path = '';
@property({ type: Boolean })
canGoBack = false;
@ -177,10 +177,10 @@ export class FourOhFourPage extends LitElement {
href="javascript:void(0)"
@click="${() =>
this.dispatchEvent(
new CustomEvent("go-back", {
new CustomEvent('go-back', {
bubbles: true,
composed: true,
})
}),
)}"
class="primary-button"
>

View file

@ -1,12 +1,12 @@
import { getNpub, getUserProfile } from "@/ndk";
import type { NDKUserProfile } from "@nostr-dev-kit/ndk";
import { css, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { html, literal } from "lit/static-html.js";
import "@widgets/BitcoinBlockWidget";
import "@components/AppGrid";
import { getNpub, getUserProfile } from '@/ndk';
import type { NDKUserProfile } from '@nostr-dev-kit/ndk';
import { LitElement, css } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { html, literal } from 'lit/static-html.js';
import '@widgets/BitcoinBlockWidget';
import '@components/AppGrid';
@customElement("arx-eve-home")
@customElement('arx-eve-home')
export class Home extends LitElement {
@state()
private npub: string | undefined;
@ -18,72 +18,72 @@ export class Home extends LitElement {
apps = [
{
id: 0,
href: "letters",
name: "Letters",
color: "#FF33BB",
icon: "bxs:envelope",
href: 'letters',
name: 'Letters',
color: '#FF33BB',
icon: 'bxs:envelope',
},
{
id: 1,
href: "messages",
name: "Messages",
color: "#34C759",
icon: "bxs:chat",
href: 'messages',
name: 'Messages',
color: '#34C759',
icon: 'bxs:chat',
},
{
id: 2,
href: "calendar",
name: "Calendar",
color: "#FF9500",
icon: "bxs:calendar",
href: 'calendar',
name: 'Calendar',
color: '#FF9500',
icon: 'bxs:calendar',
},
{
id: 3,
href: "phora",
name: "Phora",
color: "#FF3B30",
icon: "bxs:conversation",
href: 'phora',
name: 'Phora',
color: '#FF3B30',
icon: 'bxs:conversation',
},
{
id: 5,
href: "agora",
name: "Agora",
color: "#5856D6",
icon: "bxs:store",
href: 'agora',
name: 'Agora',
color: '#5856D6',
icon: 'bxs:store',
},
{
id: 6,
href: "wallet",
name: "Wallet",
color: "#007AFF",
icon: "bxs:wallet",
href: 'wallet',
name: 'Wallet',
color: '#007AFF',
icon: 'bxs:wallet',
},
{
id: 7,
href: "consortium",
name: "Consortium",
color: "#FFCC00",
icon: "bxs:landmark",
href: 'consortium',
name: 'Consortium',
color: '#FFCC00',
icon: 'bxs:landmark',
},
{
id: 8,
href: "settings",
name: "Settings",
color: "#deadbeef",
icon: "bxs:wrench",
href: 'settings',
name: 'Settings',
color: '#deadbeef',
icon: 'bxs:wrench',
},
];
widgets = [
{
title: "Bitcoin Block",
title: 'Bitcoin Block',
content: literal`arx-bitcoin-block-widget`,
},
];
async loadProperties() {
const npub = await getNpub();
if (!npub) return alert("No npub?");
if (!npub) return alert('No npub?');
this.npub = npub;
this.profile = (await getUserProfile(this.npub)) as NDKUserProfile;
this.username = this.profile?.name || this.npub.substring(0, 8);
@ -242,7 +242,7 @@ export class Home extends LitElement {
<h3>${widget.title}</h3>
<${widget.content}></${widget.content}>
</div>
`
`,
)}
</div>
</div>

View file

@ -1,13 +1,13 @@
import { LitElement, html, css } from "lit";
import { customElement, state } from "lit/decorators.js";
import { getSigner, ndk } from "@/ndk";
import formatDateTime from "@utils/formatDateTime";
import type { NDKSubscription } from "@nostr-dev-kit/ndk";
import { getSigner, ndk } from '@/ndk';
import type { NDKSubscription } from '@nostr-dev-kit/ndk';
import formatDateTime from '@utils/formatDateTime';
import { LitElement, css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import "@components/Breadcrumbs";
import "@components/PhoraForumCategory";
import "@components/PhoraForumTopic";
import "@components/PhoraButton";
import '@components/Breadcrumbs';
import '@components/PhoraForumCategory';
import '@components/PhoraForumTopic';
import '@components/PhoraButton';
interface ForumTopic {
id: string;
@ -24,7 +24,7 @@ interface ForumCategory {
topics: ForumTopic[];
}
@customElement("arx-phora-home")
@customElement('arx-phora-home')
export class PhoraForum extends LitElement {
@state()
private categories: ForumCategory[] = [];
@ -53,18 +53,14 @@ export class PhoraForum extends LitElement {
.subscribe({
kinds: [11],
})
.on("event", (event) => {
const subject = event.tags.find(
(tag: string[]) => tag[0] === "subject"
);
const parent = event.tags.find((tag: string[]) => tag[0] === "e");
.on('event', (event) => {
const subject = event.tags.find((tag: string[]) => tag[0] === 'subject');
const parent = event.tags.find((tag: string[]) => tag[0] === 'e');
if (!subject) return;
if (parent) {
const categoryIndex = this.categories.findIndex(
(category) => category.id === parent[1]
);
const categoryIndex = this.categories.findIndex((category) => category.id === parent[1]);
if (categoryIndex === -1) return;
const updatedCategories = [...this.categories];
@ -95,7 +91,7 @@ export class PhoraForum extends LitElement {
override render() {
return html`
<arx-breadcrumbs
.items=${[{ text: "Home", href: "/" }, { text: "Phora" }]}
.items=${[{ text: 'Home', href: '/' }, { text: 'Phora' }]}
>
</arx-breadcrumbs>
@ -116,10 +112,10 @@ export class PhoraForum extends LitElement {
lastPostTime=${topic.created_at}
>
</arx-phora-forum-topic>
`
`,
)}
</arx-phora-forum-category>
`
`,
)}
`;
}

View file

@ -1,15 +1,15 @@
import { LitElement, html, css } from "lit";
import { customElement, state } from "lit/decorators.js";
import { getSigner, ndk } from "@/ndk";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { getSigner, ndk } from '@/ndk';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
@customElement("arx-phora-category-creator")
@customElement('arx-phora-category-creator')
export class PhoraCategoryCreator extends LitElement {
@state()
private newCategory = "";
private newCategory = '';
@state()
private categoryDescription = "";
private categoryDescription = '';
static override styles = css`
:host {
@ -33,12 +33,12 @@ export class PhoraCategoryCreator extends LitElement {
private async doCreateCategory() {
if (this.newCategory.length < 3) {
alert("Category name must be at least 3 characters long");
alert('Category name must be at least 3 characters long');
return;
}
if (this.categoryDescription.length < 10) {
alert("Category description must be at least 10 characters long");
alert('Category description must be at least 10 characters long');
return;
}
@ -46,23 +46,23 @@ export class PhoraCategoryCreator extends LitElement {
await getSigner();
const event = new NDKEvent(ndk);
event.kind = 11;
event.tags = [["subject", this.newCategory]];
event.tags = [['subject', this.newCategory]];
event.content = this.categoryDescription;
await event.sign();
await event.publish();
this.dispatchEvent(
new CustomEvent("category-created", {
new CustomEvent('category-created', {
bubbles: true,
composed: true,
})
}),
);
this.newCategory = "";
this.categoryDescription = "";
this.newCategory = '';
this.categoryDescription = '';
} catch (error) {
console.error("Failed to create category:", error);
alert("Failed to create category");
console.error('Failed to create category:', error);
alert('Failed to create category');
}
}

View file

@ -1,15 +1,15 @@
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { getSigner, ndk } from "@/ndk";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { getSigner, ndk } from '@/ndk';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
@customElement("arx-phora-post-creator")
@customElement('arx-phora-post-creator')
export class PhoraPostCreator extends LitElement {
@property({ type: String })
topicId = "";
topicId = '';
@state()
private postContent = "";
private postContent = '';
@state()
private isCreating = false;
@ -71,7 +71,7 @@ export class PhoraPostCreator extends LitElement {
if (this.isCreating) return;
if (this.postContent.length < 10) {
this.error = "Post content must be at least 10 characters long";
this.error = 'Post content must be at least 10 characters long';
return;
}
@ -82,27 +82,27 @@ export class PhoraPostCreator extends LitElement {
await getSigner();
const event = new NDKEvent(ndk);
event.kind = 1111;
event.tags = [["e", this.topicId]];
event.tags = [['e', this.topicId]];
event.content = this.postContent;
await event.sign();
await event.publish();
this.dispatchEvent(
new CustomEvent("post-created", {
new CustomEvent('post-created', {
bubbles: true,
composed: true,
detail: {
postId: event.id,
topicId: this.topicId,
},
})
}),
);
// Reset form
this.postContent = "";
this.postContent = '';
} catch (error) {
console.error("Failed to create post:", error);
this.error = "Failed to create post. Please try again.";
console.error('Failed to create post:', error);
this.error = 'Failed to create post. Please try again.';
} finally {
this.isCreating = false;
}
@ -131,7 +131,7 @@ export class PhoraPostCreator extends LitElement {
<div class="actions">
<arx-phora-button
@click=${() => this.dispatchEvent(new CustomEvent("cancel"))}
@click=${() => this.dispatchEvent(new CustomEvent('cancel'))}
?disabled=${this.isCreating}
>
Cancel
@ -141,7 +141,7 @@ export class PhoraPostCreator extends LitElement {
@click=${this.doCreatePost}
?disabled=${this.isCreating}
>
${this.isCreating ? "Creating..." : "Create"}
${this.isCreating ? 'Creating...' : 'Create'}
</arx-phora-button>
</div>
</div>

View file

@ -1,18 +1,18 @@
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { getSigner, ndk } from "@/ndk";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { getSigner, ndk } from '@/ndk';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
@customElement("arx-phora-topic-creator")
@customElement('arx-phora-topic-creator')
export class PhoraTopicCreator extends LitElement {
@property({ type: String })
categoryId = "";
categoryId = '';
@state()
private newTopic = "";
private newTopic = '';
@state()
private topicContent = "";
private topicContent = '';
@state()
private isCreating = false;
@ -52,12 +52,12 @@ export class PhoraTopicCreator extends LitElement {
if (this.isCreating) return;
if (this.newTopic.length < 3) {
alert("Topic title must be at least 3 characters long");
alert('Topic title must be at least 3 characters long');
return;
}
if (this.topicContent.length < 10) {
alert("Topic content must be at least 10 characters long");
alert('Topic content must be at least 10 characters long');
return;
}
@ -68,29 +68,29 @@ export class PhoraTopicCreator extends LitElement {
const event = new NDKEvent(ndk);
event.kind = 11;
event.tags = [
["subject", this.newTopic],
["e", this.categoryId],
['subject', this.newTopic],
['e', this.categoryId],
];
event.content = this.topicContent;
await event.sign();
await event.publish();
this.dispatchEvent(
new CustomEvent("topic-created", {
new CustomEvent('topic-created', {
bubbles: true,
composed: true,
detail: {
topicId: event.id,
categoryId: this.categoryId,
},
})
}),
);
this.newTopic = "";
this.topicContent = "";
this.newTopic = '';
this.topicContent = '';
} catch (error) {
console.error("Failed to create topic:", error);
alert("Failed to create topic");
console.error('Failed to create topic:', error);
alert('Failed to create topic');
} finally {
this.isCreating = false;
}
@ -125,7 +125,7 @@ export class PhoraTopicCreator extends LitElement {
<div class="button-group">
<arx-phora-button
@click=${() => this.dispatchEvent(new CustomEvent("cancel"))}
@click=${() => this.dispatchEvent(new CustomEvent('cancel'))}
?disabled=${this.isCreating}
>
Cancel
@ -135,7 +135,7 @@ export class PhoraTopicCreator extends LitElement {
@click=${this.doCreateTopic}
?disabled=${this.isCreating}
>
${this.isCreating ? "Creating..." : "Create"}
${this.isCreating ? 'Creating...' : 'Create'}
</arx-phora-button>
</div>
`;

View file

@ -1,11 +1,11 @@
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { getSigner, ndk } from "@/ndk";
import type { NDKSubscription } from "@nostr-dev-kit/ndk";
import { getSigner, ndk } from '@/ndk';
import type { NDKSubscription } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import "@components/Breadcrumbs";
import "@components/ForumPost";
import "@components/PhoraButton";
import '@components/Breadcrumbs';
import '@components/ForumPost';
import '@components/PhoraButton';
interface ForumPost {
id: string;
@ -14,13 +14,13 @@ interface ForumPost {
content: string;
}
@customElement("arx-phora-topic-view")
@customElement('arx-phora-topic-view')
export class PhoraTopicView extends LitElement {
@property({ type: String })
topicId = "";
topicId = '';
@state()
override title = "";
override title = '';
@state()
private posts: ForumPost[] = [];
@ -82,10 +82,10 @@ export class PhoraTopicView extends LitElement {
const event = await ndk.fetchEvent(this.topicId);
if (!event) {
throw new Error("Could not load topic");
throw new Error('Could not load topic');
}
this.title = event.tags.find((tag) => tag[0] === "subject")?.[1] || "";
this.title = event.tags.find((tag) => tag[0] === 'subject')?.[1] || '';
this.posts = [
{
@ -100,9 +100,9 @@ export class PhoraTopicView extends LitElement {
this.subscription = ndk
.subscribe({
kinds: [1111],
"#e": [this.topicId],
'#e': [this.topicId],
})
.on("event", (event) => {
.on('event', (event) => {
this.posts = [
...this.posts,
{
@ -114,17 +114,13 @@ export class PhoraTopicView extends LitElement {
];
});
} catch (error) {
console.error("Failed to load topic:", error);
alert("Could not load topic");
console.error('Failed to load topic:', error);
alert('Could not load topic');
}
}
override render() {
const breadcrumbItems = [
{ text: "Home", href: "/" },
{ text: "Phora", href: "/phora" },
{ text: this.title },
];
const breadcrumbItems = [{ text: 'Home', href: '/' }, { text: 'Phora', href: '/phora' }, { text: this.title }];
return html`
<arx-breadcrumbs .items=${breadcrumbItems}></arx-breadcrumbs>
@ -144,7 +140,7 @@ export class PhoraTopicView extends LitElement {
.date=${post.date}
.content=${post.content}
></arx-forum-post>
`
`,
)}
</div>

View file

@ -1,13 +1,13 @@
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { when } from "lit/directives/when.js";
import type { NDKUserProfile } from "@nostr-dev-kit/ndk";
import { getUserProfile } from "../ndk";
import type { NDKUserProfile } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { when } from 'lit/directives/when.js';
import { getUserProfile } from '../ndk';
@customElement("arx-profile-route")
@customElement('arx-profile-route')
export class NostrProfile extends LitElement {
@property({ type: String })
npub = "";
npub = '';
@state()
private profile: NDKUserProfile | undefined;
@ -282,16 +282,14 @@ export class NostrProfile extends LitElement {
try {
this.profile = await getUserProfile(this.npub);
} catch (err) {
this.error = "Failed to load profile";
this.error = 'Failed to load profile';
console.error(err);
}
}
private get displayName() {
if (!this.profile) return this.npub;
return (
this.profile.displayName || this.profile.name || this.npub.substring(0, 8)
);
return this.profile.displayName || this.profile.name || this.npub.substring(0, 8);
}
override render() {
@ -313,13 +311,11 @@ export class NostrProfile extends LitElement {
</div>
<div class="banner-overlay"></div>
</div>
`
`,
)}
<div
class=${this.profile.banner
? "profile-container with-banner"
: "profile-container no-banner"}
class=${this.profile.banner ? 'profile-container with-banner' : 'profile-container no-banner'}
>
<div class="profile-card">
<div class="profile-content">
@ -339,7 +335,7 @@ export class NostrProfile extends LitElement {
class="placeholder-icon"
></svg-icon>
</div>
`
`,
)}
</div>
@ -354,7 +350,7 @@ export class NostrProfile extends LitElement {
<span class="verified-icon">
<svg-icon icon="mdi:check-decagram"></svg-icon>
</span>
`
`,
)}
</h1>
${when(
@ -364,15 +360,12 @@ export class NostrProfile extends LitElement {
<svg-icon icon="mdi:at"></svg-icon>
${this.profile!.nip05}
</p>
`
`,
)}
</div>
</div>
${when(
this.profile.about,
() => html` <p class="bio">${this.profile!.about}</p> `
)}
${when(this.profile.about, () => html` <p class="bio">${this.profile!.about}</p> `)}
</div>
</div>
@ -388,7 +381,7 @@ export class NostrProfile extends LitElement {
<svg-icon icon="mdi:web" class="link-icon website"></svg-icon>
<span>${this.profile!.website}</span>
</a>
`
`,
)}
${when(
this.profile.lud16,
@ -400,7 +393,7 @@ export class NostrProfile extends LitElement {
></svg-icon>
<span>${this.profile!.lud16}</span>
</a>
`
`,
)}
</div>
</div>

View file

@ -1,17 +1,17 @@
import "@routes/404Page";
import "@routes/Home";
import "@routes/Profile";
import "@routes/Phora/Home";
import "@routes/Phora/NewCategory";
import "@routes/Phora/NewTopic";
import "@routes/Phora/TopicView";
import "@routes/Phora/NewPost";
import "@components/InitialSetup";
import '@routes/404Page';
import '@routes/Home';
import '@routes/Profile';
import '@routes/Phora/Home';
import '@routes/Phora/NewCategory';
import '@routes/Phora/NewTopic';
import '@routes/Phora/TopicView';
import '@routes/Phora/NewPost';
import '@components/InitialSetup';
import { css, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { html, literal, type StaticValue } from "lit/static-html.js";
import { spread } from "@open-wc/lit-helpers";
import { spread } from '@open-wc/lit-helpers';
import { LitElement, css } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { type StaticValue, html, literal } from 'lit/static-html.js';
export interface RouteParams {
[key: string]: string;
@ -26,46 +26,46 @@ interface Route {
meta?: Record<string, string>;
}
@customElement("arx-eve-router")
@customElement('arx-eve-router')
export default class EveRouter extends LitElement {
private static routes: Route[] = [
{
pattern: "home",
pattern: 'home',
params: {},
component: literal`arx-eve-home`,
},
{
pattern: "profile/:npub",
pattern: 'profile/:npub',
params: {},
component: literal`arx-profile-route`,
},
{
pattern: "phora",
pattern: 'phora',
params: {},
component: literal`arx-phora-home`,
},
{
pattern: "phora/new-category",
pattern: 'phora/new-category',
params: {},
component: literal`arx-phora-category-creator`,
},
{
pattern: "phora/new-topic/:categoryId",
pattern: 'phora/new-topic/:categoryId',
params: {},
component: literal`arx-phora-topic-creator`,
},
{
pattern: "phora/topics/:topicId",
pattern: 'phora/topics/:topicId',
params: {},
component: literal`arx-phora-topic-view`,
},
{
pattern: "phora/new-post/:topicId",
pattern: 'phora/new-post/:topicId',
params: {},
component: literal`arx-phora-post-creator`,
},
{
pattern: "404",
pattern: '404',
params: {},
component: literal`arx-404-page`,
},
@ -116,8 +116,7 @@ export default class EveRouter extends LitElement {
constructor() {
super();
this.initializeRouter();
if (this.ccnSetup)
window.relay.start(localStorage.getItem("encryption_key")!);
if (this.ccnSetup) window.relay.start(localStorage.getItem('encryption_key')!);
}
override connectedCallback(): void {
@ -127,8 +126,8 @@ export default class EveRouter extends LitElement {
override disconnectedCallback(): void {
super.disconnectedCallback();
window.removeEventListener("hashchange", this.handleHashChange);
window.removeEventListener("popstate", this.handlePopState);
window.removeEventListener('hashchange', this.handleHashChange);
window.removeEventListener('popstate', this.handlePopState);
}
private initializeRouter(): void {
@ -139,8 +138,8 @@ export default class EveRouter extends LitElement {
}
private setupEventListeners(): void {
window.addEventListener("hashchange", this.handleHashChange.bind(this));
window.addEventListener("popstate", this.handlePopState.bind(this));
window.addEventListener('hashchange', this.handleHashChange.bind(this));
window.addEventListener('popstate', this.handlePopState.bind(this));
}
private handleHashChange(): void {
@ -161,12 +160,9 @@ export default class EveRouter extends LitElement {
this.currentIndex = this.history.length - 1;
}
private matchRoute(
pattern: string,
path: string
): { isMatch: boolean; params: RouteParams } {
const patternParts = pattern.split("/").filter(Boolean);
const pathParts = path.split("/").filter(Boolean);
private matchRoute(pattern: string, path: string): { isMatch: boolean; params: RouteParams } {
const patternParts = pattern.split('/').filter(Boolean);
const pathParts = path.split('/').filter(Boolean);
const params: RouteParams = {};
if (patternParts.length !== pathParts.length) {
@ -175,7 +171,7 @@ export default class EveRouter extends LitElement {
const isMatch = patternParts.every((patternPart, index) => {
const pathPart = pathParts[index];
if (patternPart.startsWith(":")) {
if (patternPart.startsWith(':')) {
const paramName = patternPart.slice(1);
params[paramName] = decodeURIComponent(pathPart);
return true;
@ -188,15 +184,12 @@ export default class EveRouter extends LitElement {
get currentPath(): string {
const hash = window.location.hash?.slice(1);
return hash === "" ? "home" : hash;
return hash === '' ? 'home' : hash;
}
get currentRoute(): Route {
const route = EveRouter.routes.find((route) => {
const { isMatch, params } = this.matchRoute(
route.pattern,
this.currentPath
);
const { isMatch, params } = this.matchRoute(route.pattern, this.currentPath);
if (isMatch) {
route.params = params;
return true;
@ -208,7 +201,7 @@ export default class EveRouter extends LitElement {
private getNotFoundRoute(): Route {
return {
pattern: "404",
pattern: '404',
params: {},
component: literal`arx-404-page`,
};