atproto explorer pdsls.dev
atproto tool

Compare changes

Choose any two refs to compare.

+1 -1
src/components/navbar.tsx
···
></span>
</button>
</Tooltip>
-
<CopyButton content={props.params.repo} label="Copy DID" />
</div>
</div>
</Show>
···
></span>
</button>
</Tooltip>
+
<CopyButton content={props.params.repo!} label="Copy DID" />
</div>
</div>
</Show>
public/headers/sonic.jpg

This is a binary file and will not be displayed.

+2 -10
src/components/backlinks.tsx
···
{(collection) => (
<div>
<div class="flex items-center gap-1">
-
<span
-
title="Collection containing linking records"
-
class="iconify lucide--book-text shrink-0"
-
></span>
{collection}
</div>
<For each={response()![collection]}>
{({ path, counts }) => (
<div class="ml-4.5">
<div class="flex items-center gap-1">
-
<span
-
title="Record path where the link is found"
-
class="iconify lucide--route shrink-0"
-
></span>
{path.slice(1)}
</div>
<div class="ml-4.5">
<p>
<button
class="text-blue-400 hover:underline active:underline"
-
title="Show linking records"
onclick={() =>
(
show()?.collection === collection &&
···
{" from "}
<button
class="text-blue-400 hover:underline active:underline"
-
title="Show linking DIDs"
onclick={() =>
(
show()?.collection === collection &&
···
{(collection) => (
<div>
<div class="flex items-center gap-1">
+
<span class="iconify lucide--book-text shrink-0"></span>
{collection}
</div>
<For each={response()![collection]}>
{({ path, counts }) => (
<div class="ml-4.5">
<div class="flex items-center gap-1">
+
<span class="iconify lucide--route shrink-0"></span>
{path.slice(1)}
</div>
<div class="ml-4.5">
<p>
<button
class="text-blue-400 hover:underline active:underline"
onclick={() =>
(
show()?.collection === collection &&
···
{" from "}
<button
class="text-blue-400 hover:underline active:underline"
onclick={() =>
(
show()?.collection === collection &&
+1 -1
src/components/text-input.tsx
···
disabled={props.disabled}
required={props.required}
class={
-
"dark:bg-dark-100 dark:shadow-dark-700 rounded-lg border-[0.5px] border-neutral-300 bg-white px-2 py-1 shadow-xs select-none placeholder:text-sm focus:outline-[1px] focus:outline-neutral-600 dark:border-neutral-600 dark:focus:outline-neutral-400 " +
props.class
}
onInput={props.onInput}
···
disabled={props.disabled}
required={props.required}
class={
+
"dark:bg-dark-100 dark:inset-shadow-dark-200 rounded-lg border-[0.5px] border-neutral-300 bg-white px-2 py-1 inset-shadow-xs select-none placeholder:text-sm focus:outline-[1px] focus:outline-neutral-600 dark:border-neutral-600 dark:focus:outline-neutral-400 " +
props.class
}
onInput={props.onInput}
+2 -2
src/views/stream.tsx
···
spellcheck={false}
placeholder="Comma-separated list of collections"
value={searchParams.collections ?? ""}
-
class="dark:bg-dark-100 dark:shadow-dark-700 grow rounded-lg border-[0.5px] border-neutral-300 bg-white px-2 py-1 shadow-xs focus:outline-[1px] focus:outline-neutral-600 dark:border-neutral-600 dark:focus:outline-neutral-400"
/>
</label>
</Show>
···
spellcheck={false}
placeholder="Comma-separated list of DIDs"
value={searchParams.dids ?? ""}
-
class="dark:bg-dark-100 dark:shadow-dark-700 grow rounded-lg border-[0.5px] border-neutral-300 bg-white px-2 py-1 shadow-xs focus:outline-[1px] focus:outline-neutral-600 dark:border-neutral-600 dark:focus:outline-neutral-400"
/>
</label>
</Show>
···
spellcheck={false}
placeholder="Comma-separated list of collections"
value={searchParams.collections ?? ""}
+
class="dark:bg-dark-100 dark:inset-shadow-dark-200 grow rounded-lg border-[0.5px] border-neutral-300 bg-white px-2 py-1 inset-shadow-xs focus:outline-[1px] focus:outline-neutral-600 dark:border-neutral-600 dark:focus:outline-neutral-400"
/>
</label>
</Show>
···
spellcheck={false}
placeholder="Comma-separated list of DIDs"
value={searchParams.dids ?? ""}
+
class="dark:bg-dark-100 dark:inset-shadow-dark-200 grow rounded-lg border-[0.5px] border-neutral-300 bg-white px-2 py-1 inset-shadow-xs focus:outline-[1px] focus:outline-neutral-600 dark:border-neutral-600 dark:focus:outline-neutral-400"
/>
</label>
</Show>
-66
src/utils/types/at-uri.ts
···
-
type Did<TMethod extends string = string> = `did:${TMethod}:${string}`;
-
-
type Nsid = `${string}.${string}.${string}`;
-
-
type RecordKey = string;
-
-
const DID_RE = /^did:([a-z]+):([a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-])$/;
-
-
const NSID_RE =
-
/^[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?:\.[a-zA-Z](?:[a-zA-Z0-9]{0,62})?)$/;
-
-
const RECORD_KEY_RE = /^(?!\.{1,2}$)[a-zA-Z0-9_~.:-]{1,512}$/;
-
-
export const ATURI_RE =
-
/^at:\/\/([a-zA-Z0-9._:%-]+)(?:\/([a-zA-Z0-9-.]+)(?:\/([a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(?:#(\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/;
-
-
const isDid = (input: unknown): input is Did => {
-
return (
-
typeof input === "string" && input.length >= 7 && input.length <= 2048 && DID_RE.test(input)
-
);
-
};
-
-
const isNsid = (input: unknown): input is Nsid => {
-
return (
-
typeof input === "string" && input.length >= 5 && input.length <= 317 && NSID_RE.test(input)
-
);
-
};
-
-
const isRecordKey = (input: unknown): input is RecordKey => {
-
return (
-
typeof input === "string" &&
-
input.length >= 1 &&
-
input.length <= 512 &&
-
RECORD_KEY_RE.test(input)
-
);
-
};
-
-
export interface AddressedAtUri {
-
repo: Did;
-
collection: Nsid;
-
rkey: string;
-
fragment: string | undefined;
-
}
-
-
export const parseAddressedAtUri = (str: string): AddressedAtUri => {
-
const match = ATURI_RE.exec(str);
-
assert(match !== null, `invalid addressed-at-uri: ${str}`);
-
-
const [, r, c, k, f] = match;
-
assert(isDid(r), `invalid repo in addressed-at-uri: ${r}`);
-
assert(isNsid(c), `invalid collection in addressed-at-uri: ${c}`);
-
assert(isRecordKey(k), `invalid rkey in addressed-at-uri: ${k}`);
-
-
return {
-
repo: r,
-
collection: c,
-
rkey: k,
-
fragment: f,
-
};
-
};
-
-
function assert(condition: boolean, message: string): asserts condition {
-
if (!condition) {
-
throw new Error(message);
-
}
-
}
···
+22 -1
vite.config.ts
···
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";
import metadata from "./public/oauth-client-metadata.json";
···
const SERVER_HOST = "127.0.0.1";
const SERVER_PORT = 13213;
export default defineConfig({
plugins: [
tailwindcss(),
solidPlugin(),
-
// Injects OAuth-related variables
{
name: "oauth",
config(_conf, { command }) {
···
process.env.VITE_CLIENT_URI = metadata.client_uri;
process.env.VITE_OAUTH_SCOPE = metadata.scope;
},
},
],
···
import tailwindcss from "@tailwindcss/vite";
+
import { execSync } from "child_process";
import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";
import metadata from "./public/oauth-client-metadata.json";
···
const SERVER_HOST = "127.0.0.1";
const SERVER_PORT = 13213;
+
const getVersion = (): string => {
+
try {
+
const describe = execSync("git describe --tags --long --dirty --always").toString().trim();
+
+
const match = describe.match(/^v?(.+?)-(\d+)-g([a-f0-9]+)(-dirty)?$/);
+
+
if (match) {
+
const [, version, commits, hash, dirty] = match;
+
if (commits === "0") {
+
return `v${version}${dirty ? "-dirty" : ""}`;
+
}
+
return `v${version}.dev${commits}+g${hash}${dirty ? "-dirty" : ""}`;
+
}
+
+
return `v0.0.0.dev+g${describe}`;
+
} catch {
+
return "v0.0.0-unknown";
+
}
+
};
+
export default defineConfig({
plugins: [
tailwindcss(),
solidPlugin(),
{
name: "oauth",
config(_conf, { command }) {
···
process.env.VITE_CLIENT_URI = metadata.client_uri;
process.env.VITE_OAUTH_SCOPE = metadata.scope;
+
process.env.VITE_APP_VERSION = getVersion();
},
},
],
-10
src/utils/app-urls.ts
···
export enum App {
Bluesky,
Tangled,
-
Whitewind,
Frontpage,
Pinksea,
Linkat,
···
export const appName = {
[App.Bluesky]: "Bluesky",
[App.Tangled]: "Tangled",
-
[App.Whitewind]: "Whitewind",
[App.Frontpage]: "Frontpage",
[App.Pinksea]: "Pinksea",
[App.Linkat]: "Linkat",
···
"main.bsky.dev": App.Bluesky,
"social.daniela.lol": App.Bluesky,
"tangled.org": App.Tangled,
-
"whtwnd.com": App.Whitewind,
"frontpage.fyi": App.Frontpage,
"pinksea.art": App.Pinksea,
"linkat.blue": App.Linkat,
···
return `at://${user}`;
},
-
[App.Whitewind]: (path) => {
-
if (path.length === 2) {
-
return `at://${path[0]}/com.whtwnd.blog.entry/${path[1]}`;
-
}
-
-
return `at://${path[0]}/com.whtwnd.blog.entry`;
-
},
[App.Frontpage]: (path) => {
if (path.length === 3) {
return `at://${path[1]}/fyi.unravel.frontpage.post/${path[2]}`;
···
export enum App {
Bluesky,
Tangled,
Frontpage,
Pinksea,
Linkat,
···
export const appName = {
[App.Bluesky]: "Bluesky",
[App.Tangled]: "Tangled",
[App.Frontpage]: "Frontpage",
[App.Pinksea]: "Pinksea",
[App.Linkat]: "Linkat",
···
"main.bsky.dev": App.Bluesky,
"social.daniela.lol": App.Bluesky,
"tangled.org": App.Tangled,
"frontpage.fyi": App.Frontpage,
"pinksea.art": App.Pinksea,
"linkat.blue": App.Linkat,
···
return `at://${user}`;
},
[App.Frontpage]: (path) => {
if (path.length === 3) {
return `at://${path[1]}/fyi.unravel.frontpage.post/${path[2]}`;
+1 -1
src/components/theme.tsx
···
"flex items-center gap-2 rounded-xl border px-3 py-2": true,
"bg-neutral-200/60 border-neutral-300 dark:border-neutral-500 dark:bg-neutral-700":
theme() === props.theme,
-
"border-neutral-200 dark:border-neutral-600 hover:bg-neutral-100 dark:hover:bg-neutral-800":
theme() !== props.theme,
}}
onclick={() => updateTheme(props.theme)}
···
"flex items-center gap-2 rounded-xl border px-3 py-2": true,
"bg-neutral-200/60 border-neutral-300 dark:border-neutral-500 dark:bg-neutral-700":
theme() === props.theme,
+
"border-neutral-200 dark:border-neutral-600 hover:bg-neutral-200/30 dark:hover:bg-neutral-800":
theme() !== props.theme,
}}
onclick={() => updateTheme(props.theme)}
+7 -1
src/components/video-player.tsx
···
});
return (
-
<video ref={video} class="max-h-80 max-w-[20rem]" controls playsinline onLoadedData={props.onLoad}>
<source type="video/mp4" />
</video>
);
···
});
return (
+
<video
+
ref={video}
+
class="max-h-80 max-w-[20rem]"
+
controls
+
playsinline
+
onLoadedData={props.onLoad}
+
>
<source type="video/mp4" />
</video>
);
+15 -15
src/utils/hooks/debounced.ts
···
-
import { type Accessor, createEffect, createSignal, onCleanup } from 'solid-js';
export const createDebouncedValue = <T>(
-
accessor: Accessor<T>,
-
delay: number,
-
equals?: false | ((prev: T, next: T) => boolean),
): Accessor<T> => {
-
const initial = accessor();
-
const [state, setState] = createSignal(initial, { equals });
-
createEffect((prev: T) => {
-
const next = accessor();
-
if (prev !== next) {
-
const timeout = setTimeout(() => setState(() => next), delay);
-
onCleanup(() => clearTimeout(timeout));
-
}
-
return next;
-
}, initial);
-
return state;
};
···
+
import { type Accessor, createEffect, createSignal, onCleanup } from "solid-js";
export const createDebouncedValue = <T>(
+
accessor: Accessor<T>,
+
delay: number,
+
equals?: false | ((prev: T, next: T) => boolean),
): Accessor<T> => {
+
const initial = accessor();
+
const [state, setState] = createSignal(initial, { equals });
+
createEffect((prev: T) => {
+
const next = accessor();
+
if (prev !== next) {
+
const timeout = setTimeout(() => setState(() => next), delay);
+
onCleanup(() => clearTimeout(timeout));
+
}
+
return next;
+
}, initial);
+
return state;
};
+38 -12
src/components/dropdown.tsx
···
Show,
useContext,
} from "solid-js";
import { addToClipboard } from "../utils/copy";
const MenuContext = createContext<{
···
const ctx = useContext(MenuContext);
const [menu, setMenu] = createSignal<HTMLDivElement>();
const [menuButton, setMenuButton] = createSignal<HTMLButtonElement>();
const clickEvent = (event: MouseEvent) => {
const target = event.target as Node;
if (!menuButton()?.contains(target) && !menu()?.contains(target)) ctx?.setShowMenu(false);
};
-
onMount(() => window.addEventListener("click", clickEvent));
-
onCleanup(() => window.removeEventListener("click", clickEvent));
return (
<div class="relative">
···
props.buttonClass
}
ref={setMenuButton}
-
onClick={() => ctx?.setShowMenu(!ctx?.showMenu())}
>
<span class={"iconify " + props.icon}></span>
</button>
<Show when={ctx?.showMenu()}>
-
<div
-
ref={setMenu}
-
class={
-
"dark:bg-dark-300 dark:shadow-dark-700 absolute right-0 z-40 flex min-w-40 flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-2 shadow-md dark:border-neutral-700 " +
-
props.menuClass
-
}
-
>
-
{props.children}
-
</div>
</Show>
</div>
);
···
Show,
useContext,
} from "solid-js";
+
import { Portal } from "solid-js/web";
import { addToClipboard } from "../utils/copy";
const MenuContext = createContext<{
···
const ctx = useContext(MenuContext);
const [menu, setMenu] = createSignal<HTMLDivElement>();
const [menuButton, setMenuButton] = createSignal<HTMLButtonElement>();
+
const [buttonRect, setButtonRect] = createSignal<DOMRect>();
const clickEvent = (event: MouseEvent) => {
const target = event.target as Node;
if (!menuButton()?.contains(target) && !menu()?.contains(target)) ctx?.setShowMenu(false);
};
+
const updatePosition = () => {
+
const rect = menuButton()?.getBoundingClientRect();
+
if (rect) setButtonRect(rect);
+
};
+
+
onMount(() => {
+
window.addEventListener("click", clickEvent);
+
window.addEventListener("scroll", updatePosition, true);
+
window.addEventListener("resize", updatePosition);
+
});
+
+
onCleanup(() => {
+
window.removeEventListener("click", clickEvent);
+
window.removeEventListener("scroll", updatePosition, true);
+
window.removeEventListener("resize", updatePosition);
+
});
return (
<div class="relative">
···
props.buttonClass
}
ref={setMenuButton}
+
onClick={() => {
+
updatePosition();
+
ctx?.setShowMenu(!ctx?.showMenu());
+
}}
>
<span class={"iconify " + props.icon}></span>
</button>
<Show when={ctx?.showMenu()}>
+
<Portal>
+
<div
+
ref={setMenu}
+
style={{
+
position: "fixed",
+
top: `${(buttonRect()?.bottom ?? 0) + 4}px`,
+
left: `${(buttonRect()?.right ?? 0) - 160}px`,
+
}}
+
class={
+
"dark:bg-dark-300 dark:shadow-dark-700 z-50 flex min-w-40 flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-2 text-sm shadow-md dark:border-neutral-700 " +
+
props.menuClass
+
}
+
>
+
{props.children}
+
</div>
+
</Portal>
</Show>
</div>
);
+30
src/utils/key.ts
···
···
+
import { parseDidKey, parsePublicMultikey } from "@atcute/crypto";
+
import { fromBase58Btc } from "@atcute/multibase";
+
+
export const detectKeyType = (key: string): string => {
+
try {
+
return parsePublicMultikey(key).type;
+
} catch (e) {
+
try {
+
const bytes = fromBase58Btc(key.startsWith("z") ? key.slice(1) : key);
+
if (bytes.length >= 2) {
+
const type = (bytes[0] << 8) | bytes[1];
+
if (type === 0xed01) {
+
return "ed25519";
+
}
+
}
+
} catch {}
+
return "unknown";
+
}
+
};
+
+
export const detectDidKeyType = (key: string): string => {
+
try {
+
return parseDidKey(key).type;
+
} catch (e) {
+
if (key.startsWith("did:key:")) {
+
return detectKeyType(key.slice(8));
+
}
+
return "unknown";
+
}
+
};
+1 -1
public/oauth-client-metadata.json
···
"client_uri": "https://pdsls.dev",
"logo_uri": "https://pdsls.dev/favicon.ico",
"redirect_uris": ["https://pdsls.dev/"],
-
"scope": "atproto transition:generic",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none",
···
"client_uri": "https://pdsls.dev",
"logo_uri": "https://pdsls.dev/favicon.ico",
"redirect_uris": ["https://pdsls.dev/"],
+
"scope": "atproto repo:*?action=create repo:*?action=update repo:*?action=delete blob:*/*",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none",
+13
src/auth/oauth-config.ts
···
···
+
import { configureOAuth, defaultIdentityResolver } from "@atcute/oauth-browser-client";
+
import { didDocumentResolver, handleResolver } from "../utils/api";
+
+
configureOAuth({
+
metadata: {
+
client_id: import.meta.env.VITE_OAUTH_CLIENT_ID,
+
redirect_uri: import.meta.env.VITE_OAUTH_REDIRECT_URL,
+
},
+
identityResolver: defaultIdentityResolver({
+
handleResolver: handleResolver,
+
didDocumentResolver: didDocumentResolver,
+
}),
+
});
+77
src/auth/scope-flow.ts
···
···
+
import { isDid, isHandle } from "@atcute/lexicons/syntax";
+
import { createAuthorizationUrl } from "@atcute/oauth-browser-client";
+
import { createSignal } from "solid-js";
+
+
interface UseOAuthScopeFlowOptions {
+
onError?: (error: unknown) => void;
+
onRedirecting?: () => void;
+
beforeRedirect?: (account: string) => Promise<void>;
+
}
+
+
export const useOAuthScopeFlow = (options: UseOAuthScopeFlowOptions = {}) => {
+
const [showScopeSelector, setShowScopeSelector] = createSignal(false);
+
const [pendingAccount, setPendingAccount] = createSignal("");
+
const [shouldForceRedirect, setShouldForceRedirect] = createSignal(false);
+
+
const initiate = (account: string) => {
+
if (!account) return;
+
setPendingAccount(account);
+
setShouldForceRedirect(false);
+
setShowScopeSelector(true);
+
};
+
+
const initiateWithRedirect = (account: string) => {
+
if (!account) return;
+
setPendingAccount(account);
+
setShouldForceRedirect(true);
+
setShowScopeSelector(true);
+
};
+
+
const complete = async (scopeString: string, scopeIds: string) => {
+
try {
+
const account = pendingAccount();
+
+
if (options.beforeRedirect && !shouldForceRedirect()) {
+
try {
+
await options.beforeRedirect(account);
+
setShowScopeSelector(false);
+
return;
+
} catch {}
+
}
+
+
localStorage.setItem("pendingScopes", scopeIds);
+
+
options.onRedirecting?.();
+
+
const authUrl = await createAuthorizationUrl({
+
scope: scopeString,
+
target:
+
isHandle(account) || isDid(account) ?
+
{ type: "account", identifier: account }
+
: { type: "pds", serviceUrl: account },
+
});
+
+
await new Promise((resolve) => setTimeout(resolve, 250));
+
location.assign(authUrl);
+
} catch (e) {
+
console.error(e);
+
options.onError?.(e);
+
setShowScopeSelector(false);
+
}
+
};
+
+
const cancel = () => {
+
setShowScopeSelector(false);
+
setPendingAccount("");
+
setShouldForceRedirect(false);
+
};
+
+
return {
+
showScopeSelector,
+
pendingAccount,
+
initiate,
+
initiateWithRedirect,
+
complete,
+
cancel,
+
};
+
};
+53
src/auth/scope-utils.ts
···
···
+
import { agent, sessions } from "./state";
+
+
export const GRANULAR_SCOPES = [
+
{
+
id: "create",
+
scope: "repo:*?action=create",
+
label: "Create records",
+
},
+
{
+
id: "update",
+
scope: "repo:*?action=update",
+
label: "Update records",
+
},
+
{
+
id: "delete",
+
scope: "repo:*?action=delete",
+
label: "Delete records",
+
},
+
{
+
id: "blob",
+
scope: "blob:*/*",
+
label: "Upload blobs",
+
},
+
];
+
+
export const BASE_SCOPES = ["atproto"];
+
+
export const buildScopeString = (selected: Set<string>): string => {
+
const granular = GRANULAR_SCOPES.filter((s) => selected.has(s.id)).map((s) => s.scope);
+
return [...BASE_SCOPES, ...granular].join(" ");
+
};
+
+
export const scopeIdsToString = (scopeIds: Set<string>): string => {
+
return ["atproto", ...Array.from(scopeIds)].join(",");
+
};
+
+
export const parseScopeString = (scopeIdsString: string): Set<string> => {
+
if (!scopeIdsString) return new Set();
+
const ids = scopeIdsString.split(",").filter(Boolean);
+
return new Set(ids.filter((id) => id !== "atproto"));
+
};
+
+
export const hasScope = (grantedScopes: string | undefined, scopeId: string): boolean => {
+
if (!grantedScopes) return false;
+
return grantedScopes.split(",").includes(scopeId);
+
};
+
+
export const hasUserScope = (scopeId: string): boolean => {
+
if (!agent()) return false;
+
const grantedScopes = sessions[agent()!.sub]?.grantedScopes;
+
if (!grantedScopes) return true;
+
return hasScope(grantedScopes, scopeId);
+
};
+14
src/auth/state.ts
···
···
+
import { OAuthUserAgent } from "@atcute/oauth-browser-client";
+
import { createSignal } from "solid-js";
+
import { createStore } from "solid-js/store";
+
+
export type Account = {
+
signedIn: boolean;
+
handle?: string;
+
grantedScopes?: string;
+
};
+
+
export type Sessions = Record<string, Account>;
+
+
export const [agent, setAgent] = createSignal<OAuthUserAgent | undefined>();
+
export const [sessions, setSessions] = createStore<Sessions>();
-143
src/components/login.tsx
···
-
import { Client } from "@atcute/client";
-
import { Did } from "@atcute/lexicons";
-
import { isDid, isHandle } from "@atcute/lexicons/syntax";
-
import {
-
configureOAuth,
-
createAuthorizationUrl,
-
defaultIdentityResolver,
-
finalizeAuthorization,
-
getSession,
-
OAuthUserAgent,
-
type Session,
-
} from "@atcute/oauth-browser-client";
-
import { createSignal, Show } from "solid-js";
-
import { didDocumentResolver, handleResolver } from "../utils/api";
-
-
configureOAuth({
-
metadata: {
-
client_id: import.meta.env.VITE_OAUTH_CLIENT_ID,
-
redirect_uri: import.meta.env.VITE_OAUTH_REDIRECT_URL,
-
},
-
identityResolver: defaultIdentityResolver({
-
handleResolver: handleResolver,
-
didDocumentResolver: didDocumentResolver,
-
}),
-
});
-
-
export const [agent, setAgent] = createSignal<OAuthUserAgent | undefined>();
-
-
type Account = {
-
signedIn: boolean;
-
handle?: string;
-
};
-
-
export type Sessions = Record<string, Account>;
-
-
const Login = () => {
-
const [notice, setNotice] = createSignal("");
-
const [loginInput, setLoginInput] = createSignal("");
-
-
const login = async (handle: string) => {
-
try {
-
setNotice("");
-
if (!handle) return;
-
setNotice(`Contacting your data server...`);
-
const authUrl = await createAuthorizationUrl({
-
scope: import.meta.env.VITE_OAUTH_SCOPE,
-
target:
-
isHandle(handle) || isDid(handle) ?
-
{ type: "account", identifier: handle }
-
: { type: "pds", serviceUrl: handle },
-
});
-
-
setNotice(`Redirecting...`);
-
await new Promise((resolve) => setTimeout(resolve, 250));
-
-
location.assign(authUrl);
-
} catch (e) {
-
console.error(e);
-
setNotice(`${e}`);
-
}
-
};
-
-
return (
-
<form class="flex flex-col gap-y-2 px-1" onsubmit={(e) => e.preventDefault()}>
-
<label for="username" class="hidden">
-
Add account
-
</label>
-
<div class="dark:bg-dark-100 dark:inset-shadow-dark-200 flex grow items-center gap-2 rounded-lg border-[0.5px] border-neutral-300 bg-white px-2 inset-shadow-xs focus-within:outline-[1px] focus-within:outline-neutral-600 dark:border-neutral-600 dark:focus-within:outline-neutral-400">
-
<label
-
for="username"
-
class="iconify lucide--user-round-plus shrink-0 text-neutral-500 dark:text-neutral-400"
-
></label>
-
<input
-
type="text"
-
spellcheck={false}
-
placeholder="user.bsky.social"
-
id="username"
-
name="username"
-
autocomplete="username"
-
aria-label="Your AT Protocol handle"
-
class="grow py-1 select-none placeholder:text-sm focus:outline-none"
-
onInput={(e) => setLoginInput(e.currentTarget.value)}
-
/>
-
<button
-
onclick={() => login(loginInput())}
-
class="flex items-center rounded-md p-1 hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-600 dark:active:bg-neutral-500"
-
>
-
<span class="iconify lucide--log-in"></span>
-
</button>
-
</div>
-
<Show when={notice()}>
-
<div class="text-sm">{notice()}</div>
-
</Show>
-
</form>
-
);
-
};
-
-
const retrieveSession = async () => {
-
const init = async (): Promise<Session | undefined> => {
-
const params = new URLSearchParams(location.hash.slice(1));
-
-
if (params.has("state") && (params.has("code") || params.has("error"))) {
-
history.replaceState(null, "", location.pathname + location.search);
-
-
const auth = await finalizeAuthorization(params);
-
const did = auth.session.info.sub;
-
-
localStorage.setItem("lastSignedIn", did);
-
-
const sessions = localStorage.getItem("sessions");
-
const newSessions: Sessions = sessions ? JSON.parse(sessions) : { [did]: {} };
-
newSessions[did] = { signedIn: true };
-
localStorage.setItem("sessions", JSON.stringify(newSessions));
-
return auth.session;
-
} else {
-
const lastSignedIn = localStorage.getItem("lastSignedIn");
-
-
if (lastSignedIn) {
-
const sessions = localStorage.getItem("sessions");
-
const newSessions: Sessions = sessions ? JSON.parse(sessions) : {};
-
try {
-
const session = await getSession(lastSignedIn as Did);
-
const rpc = new Client({ handler: new OAuthUserAgent(session) });
-
const res = await rpc.get("com.atproto.server.getSession");
-
newSessions[lastSignedIn].signedIn = true;
-
localStorage.setItem("sessions", JSON.stringify(newSessions));
-
if (!res.ok) throw res.data.error;
-
return session;
-
} catch (err) {
-
newSessions[lastSignedIn].signedIn = false;
-
localStorage.setItem("sessions", JSON.stringify(newSessions));
-
throw err;
-
}
-
}
-
}
-
};
-
-
const session = await init();
-
-
if (session) setAgent(new OAuthUserAgent(session));
-
};
-
-
export { Login, retrieveSession };
···
+2 -2
src/views/collection.tsx
···
import { ComAtprotoRepoApplyWrites, ComAtprotoRepoGetRecord } from "@atcute/atproto";
-
import { Client, CredentialManager } from "@atcute/client";
import { $type, ActorIdentifier, InferXRPCBodyOutput } from "@atcute/lexicons";
import * as TID from "@atcute/tid";
import { A, useParams } from "@solidjs/router";
···
const fetchRecords = async () => {
if (!pds) pds = await resolvePDS(did!);
-
if (!rpc) rpc = new Client({ handler: new CredentialManager({ service: pds }) });
const res = await rpc.get("com.atproto.repo.listRecords", {
params: {
repo: did as ActorIdentifier,
···
import { ComAtprotoRepoApplyWrites, ComAtprotoRepoGetRecord } from "@atcute/atproto";
+
import { Client, simpleFetchHandler } from "@atcute/client";
import { $type, ActorIdentifier, InferXRPCBodyOutput } from "@atcute/lexicons";
import * as TID from "@atcute/tid";
import { A, useParams } from "@solidjs/router";
···
const fetchRecords = async () => {
if (!pds) pds = await resolvePDS(did!);
+
if (!rpc) rpc = new Client({ handler: simpleFetchHandler({ service: pds }) });
const res = await rpc.get("com.atproto.repo.listRecords", {
params: {
repo: did as ActorIdentifier,
+2 -2
src/views/labels.tsx
···
import { ComAtprotoLabelDefs } from "@atcute/atproto";
-
import { Client, CredentialManager } from "@atcute/client";
import { isAtprotoDid } from "@atcute/identity";
import { Handle } from "@atcute/lexicons";
import { A, useSearchParams } from "@solidjs/router";
···
await resolvePDS(did);
if (!labelerCache[did]) throw new Error("Repository is not a labeler");
rpc = new Client({
-
handler: new CredentialManager({ service: labelerCache[did] }),
});
setSearchParams({ did, uriPatterns });
···
import { ComAtprotoLabelDefs } from "@atcute/atproto";
+
import { Client, simpleFetchHandler } from "@atcute/client";
import { isAtprotoDid } from "@atcute/identity";
import { Handle } from "@atcute/lexicons";
import { A, useSearchParams } from "@solidjs/router";
···
await resolvePDS(did);
if (!labelerCache[did]) throw new Error("Repository is not a labeler");
rpc = new Client({
+
handler: simpleFetchHandler({ service: labelerCache[did] }),
});
setSearchParams({ did, uriPatterns });
+2 -2
src/views/pds.tsx
···
import { ComAtprotoServerDescribeServer, ComAtprotoSyncListRepos } from "@atcute/atproto";
-
import { Client, CredentialManager } from "@atcute/client";
import { InferXRPCBodyOutput } from "@atcute/lexicons";
import * as TID from "@atcute/tid";
import { A, useLocation, useParams } from "@solidjs/router";
···
setPDS(params.pds);
const pds =
params.pds!.startsWith("localhost") ? `http://${params.pds}` : `https://${params.pds}`;
-
const rpc = new Client({ handler: new CredentialManager({ service: pds }) });
const getVersion = async () => {
// @ts-expect-error: undocumented endpoint
···
import { ComAtprotoServerDescribeServer, ComAtprotoSyncListRepos } from "@atcute/atproto";
+
import { Client, simpleFetchHandler } from "@atcute/client";
import { InferXRPCBodyOutput } from "@atcute/lexicons";
import * as TID from "@atcute/tid";
import { A, useLocation, useParams } from "@solidjs/router";
···
setPDS(params.pds);
const pds =
params.pds!.startsWith("localhost") ? `http://${params.pds}` : `https://${params.pds}`;
+
const rpc = new Client({ handler: simpleFetchHandler({ service: pds }) });
const getVersion = async () => {
// @ts-expect-error: undocumented endpoint
public/favicon.ico

This is a binary file and will not be displayed.

public/fonts/Figtree[wght].woff2

This is a binary file and will not be displayed.

+7 -1
src/styles/index.css
···
@custom-variant dark (&:where(.dark, .dark *));
@theme {
-
--font-sans: "Inter", sans-serif;
--font-mono: "Roboto Mono", monospace;
--font-pecita: "Pecita", serif;
···
@custom-variant dark (&:where(.dark, .dark *));
+
@font-face {
+
font-family: "Figtree";
+
src: url("/fonts/Figtree[wght].woff2") format("woff2");
+
font-display: swap;
+
}
+
@theme {
+
--font-sans: "Figtree", sans-serif;
--font-mono: "Roboto Mono", monospace;
--font-pecita: "Pecita", serif;
+5 -1
src/auth/account.tsx
···
return (
<MenuProvider>
<DropdownMenu icon="lucide--ellipsis" buttonClass="rounded-md p-2">
-
<NavMenu href={`/at://${props.did}`} label="Go to repo" icon="lucide--user-round" />
<ActionMenu
icon="lucide--settings"
label="Edit permissions"
···
return (
<MenuProvider>
<DropdownMenu icon="lucide--ellipsis" buttonClass="rounded-md p-2">
+
<NavMenu
+
href={`/at://${props.did}`}
+
label={agent()?.sub === props.did ? "Go to repo (g)" : "Go to repo"}
+
icon="lucide--user-round"
+
/>
<ActionMenu
icon="lucide--settings"
label="Edit permissions"
+109
src/components/create/file-upload.tsx
···
···
+
import { Client } from "@atcute/client";
+
import { remove } from "@mary/exif-rm";
+
import { createSignal, onCleanup, Show } from "solid-js";
+
import { agent } from "../../auth/state";
+
import { Button } from "../button.jsx";
+
import { TextInput } from "../text-input.jsx";
+
import { editorInstance } from "./state";
+
+
export const FileUpload = (props: {
+
file: File;
+
blobInput: HTMLInputElement;
+
onClose: () => void;
+
}) => {
+
const [uploading, setUploading] = createSignal(false);
+
const [error, setError] = createSignal("");
+
+
onCleanup(() => (props.blobInput.value = ""));
+
+
const formatFileSize = (bytes: number) => {
+
if (bytes === 0) return "0 Bytes";
+
const k = 1024;
+
const sizes = ["Bytes", "KB", "MB", "GB"];
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
+
};
+
+
const uploadBlob = async () => {
+
let blob: Blob;
+
+
const mimetype = (document.getElementById("mimetype") as HTMLInputElement)?.value;
+
(document.getElementById("mimetype") as HTMLInputElement).value = "";
+
if (mimetype) blob = new Blob([props.file], { type: mimetype });
+
else blob = props.file;
+
+
if ((document.getElementById("exif-rm") as HTMLInputElement).checked) {
+
const exifRemoved = remove(new Uint8Array(await blob.arrayBuffer()));
+
if (exifRemoved !== null) blob = new Blob([exifRemoved], { type: blob.type });
+
}
+
+
const rpc = new Client({ handler: agent()! });
+
setUploading(true);
+
const res = await rpc.post("com.atproto.repo.uploadBlob", {
+
input: blob,
+
});
+
setUploading(false);
+
if (!res.ok) {
+
setError(res.data.error);
+
return;
+
}
+
editorInstance.view.dispatch({
+
changes: {
+
from: editorInstance.view.state.selection.main.head,
+
insert: JSON.stringify(res.data.blob, null, 2),
+
},
+
});
+
props.onClose();
+
};
+
+
return (
+
<div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] w-[20rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
+
<h2 class="mb-2 font-semibold">Upload blob</h2>
+
<div class="flex flex-col gap-2 text-sm">
+
<div class="flex flex-col gap-1">
+
<p class="flex gap-1">
+
<span class="truncate">{props.file.name}</span>
+
<span class="shrink-0 text-neutral-600 dark:text-neutral-400">
+
({formatFileSize(props.file.size)})
+
</span>
+
</p>
+
</div>
+
<div class="flex items-center gap-x-2">
+
<label for="mimetype" class="shrink-0 select-none">
+
MIME type
+
</label>
+
<TextInput id="mimetype" placeholder={props.file.type} />
+
</div>
+
<div class="flex items-center gap-1">
+
<input id="exif-rm" type="checkbox" checked />
+
<label for="exif-rm" class="select-none">
+
Remove EXIF data
+
</label>
+
</div>
+
<p class="text-xs text-neutral-600 dark:text-neutral-400">
+
Metadata will be pasted after the cursor
+
</p>
+
<Show when={error()}>
+
<span class="text-red-500 dark:text-red-400">Error: {error()}</span>
+
</Show>
+
<div class="flex justify-between gap-2">
+
<Button onClick={props.onClose}>Cancel</Button>
+
<Show when={uploading()}>
+
<div class="flex items-center gap-1">
+
<span class="iconify lucide--loader-circle animate-spin"></span>
+
<span>Uploading</span>
+
</div>
+
</Show>
+
<Show when={!uploading()}>
+
<Button
+
onClick={uploadBlob}
+
class="dark:shadow-dark-700 flex items-center gap-1 rounded-lg bg-blue-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-blue-600 active:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-500 dark:active:bg-blue-400"
+
>
+
Upload
+
</Button>
+
</Show>
+
</div>
+
</div>
+
</div>
+
);
+
};
+87
src/components/create/handle-input.tsx
···
···
+
import { Handle } from "@atcute/lexicons";
+
import { createSignal, Show } from "solid-js";
+
import { resolveHandle } from "../../utils/api";
+
import { Button } from "../button.jsx";
+
import { TextInput } from "../text-input.jsx";
+
import { editorInstance } from "./state";
+
+
export const HandleInput = (props: { onClose: () => void }) => {
+
const [resolving, setResolving] = createSignal(false);
+
const [error, setError] = createSignal("");
+
let handleFormRef!: HTMLFormElement;
+
+
const resolveDid = async (e: SubmitEvent) => {
+
e.preventDefault();
+
const formData = new FormData(handleFormRef);
+
const handleValue = formData.get("handle")?.toString().trim();
+
+
if (!handleValue) {
+
setError("Please enter a handle");
+
return;
+
}
+
+
setResolving(true);
+
setError("");
+
try {
+
const did = await resolveHandle(handleValue as Handle);
+
editorInstance.view.dispatch({
+
changes: {
+
from: editorInstance.view.state.selection.main.head,
+
insert: `"${did}"`,
+
},
+
});
+
props.onClose();
+
handleFormRef.reset();
+
} catch (err: any) {
+
setError(err.message || "Failed to resolve handle");
+
} finally {
+
setResolving(false);
+
}
+
};
+
+
return (
+
<div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] w-[20rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
+
<h2 class="mb-2 font-semibold">Insert DID from handle</h2>
+
<form ref={handleFormRef} onSubmit={resolveDid} class="flex flex-col gap-2 text-sm">
+
<div class="flex flex-col gap-1">
+
<label for="handle-input" class="select-none">
+
Handle
+
</label>
+
<TextInput id="handle-input" name="handle" placeholder="user.bsky.social" />
+
</div>
+
<p class="text-xs text-neutral-600 dark:text-neutral-400">
+
DID will be pasted after the cursor
+
</p>
+
<Show when={error()}>
+
<span class="text-red-500 dark:text-red-400">Error: {error()}</span>
+
</Show>
+
<div class="flex justify-between gap-2">
+
<Button
+
type="button"
+
onClick={() => {
+
props.onClose();
+
handleFormRef.reset();
+
setError("");
+
}}
+
>
+
Cancel
+
</Button>
+
<Show when={resolving()}>
+
<div class="flex items-center gap-1">
+
<span class="iconify lucide--loader-circle animate-spin"></span>
+
<span>Resolving</span>
+
</div>
+
</Show>
+
<Show when={!resolving()}>
+
<Button
+
type="submit"
+
class="dark:shadow-dark-700 flex items-center gap-1 rounded-lg bg-blue-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-blue-600 active:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-500 dark:active:bg-blue-400"
+
>
+
Insert
+
</Button>
+
</Show>
+
</div>
+
</form>
+
</div>
+
);
+
};
+12
src/components/create/menu-item.tsx
···
···
+
export const MenuItem = (props: { icon: string; label: string; onClick: () => void }) => {
+
return (
+
<button
+
type="button"
+
class="flex items-center gap-2 rounded-md p-2 text-left text-xs hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
+
onClick={props.onClick}
+
>
+
<span class={`iconify ${props.icon}`}></span>
+
<span>{props.label}</span>
+
</button>
+
);
+
};
+4
src/components/create/state.ts
···
···
+
import { createSignal } from "solid-js";
+
+
export const editorInstance = { view: null as any };
+
export const [placeholder, setPlaceholder] = createSignal<any>();
+1 -1
src/components/lexicon-schema.tsx
···
{props.name === "main" ? "Main Definition" : props.name}
</a>
<span class={`rounded px-2 py-0.5 text-xs font-semibold uppercase ${defTypeColor()}`}>
-
{props.def.type}
</span>
</div>
···
{props.name === "main" ? "Main Definition" : props.name}
</a>
<span class={`rounded px-2 py-0.5 text-xs font-semibold uppercase ${defTypeColor()}`}>
+
{props.def.type.replace("-", " ")}
</span>
</div>
+1
src/components/editor.tsx
···
keymap.of([indentWithTab]),
linter(jsonParseLinter()),
themeColor.of(document.documentElement.classList.contains("dark") ? basicDark : basicLight),
],
});
editorInstance.view = view;
···
keymap.of([indentWithTab]),
linter(jsonParseLinter()),
themeColor.of(document.documentElement.classList.contains("dark") ? basicDark : basicLight),
+
EditorView.lineWrapping,
],
});
editorInstance.view = view;
+29 -48
src/components/create/index.tsx
···
import { addNotification, removeNotification } from "../notification.jsx";
import { TextInput } from "../text-input.jsx";
import Tooltip from "../tooltip.jsx";
import { FileUpload } from "./file-upload";
import { HandleInput } from "./handle-input";
import { MenuItem } from "./menu-item";
···
const [openUpload, setOpenUpload] = createSignal(false);
const [openInsertMenu, setOpenInsertMenu] = createSignal(false);
const [openHandleDialog, setOpenHandleDialog] = createSignal(false);
-
const [validate, setValidate] = createSignal<boolean | undefined>(undefined);
const [isMaximized, setIsMaximized] = createSignal(false);
const [isMinimized, setIsMinimized] = createSignal(false);
const [collectionError, setCollectionError] = createSignal("");
···
};
};
-
const getValidateIcon = () => {
-
return (
-
validate() === true ? "lucide--circle-check"
-
: validate() === false ? "lucide--circle-x"
-
: "lucide--circle"
-
);
-
};
-
-
const getValidateLabel = () => {
-
return (
-
validate() === true ? "True"
-
: validate() === false ? "False"
-
: "Unset"
-
);
-
};
-
createEffect(() => {
if (openDialog()) {
-
setValidate(undefined);
setCollectionError("");
setRkeyError("");
}
});
-
const createRecord = async (formData: FormData) => {
const repo = formData.get("repo")?.toString();
if (!repo) return;
const rpc = new Client({ handler: new OAuthUserAgent(await getSession(repo as Did)) });
···
collection: collection ? collection.toString() : record.$type,
rkey: rkey?.toString().length ? rkey?.toString() : undefined,
record: record,
-
validate: validate(),
},
});
if (!res.ok) {
setNotice(`${res.data.error}: ${res.data.message}`);
return;
}
setOpenDialog(false);
const id = addNotification({
message: "Record created",
···
navigate(`/${res.data.uri}`);
};
-
const editRecord = async (recreate?: boolean) => {
const record = editorInstance.view.state.doc.toString();
if (!record) return;
const rpc = new Client({ handler: agent()! });
···
const res = await rpc.post("com.atproto.repo.applyWrites", {
input: {
repo: agent()!.sub,
-
validate: validate(),
writes: [
{
collection: params.collection as `${string}.${string}.${string}`,
···
const res = await rpc.post("com.atproto.repo.applyWrites", {
input: {
repo: agent()!.sub,
-
validate: validate(),
writes: [
{
collection: params.collection as `${string}.${string}.${string}`,
···
return;
}
}
setOpenDialog(false);
const id = addNotification({
message: "Record edited",
···
>
<HandleInput onClose={() => setOpenHandleDialog(false)} />
</Modal>
<div class="flex items-center justify-end gap-2">
-
<button
-
type="button"
-
class="flex items-center gap-1 rounded-sm p-1.5 text-xs hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
-
onClick={() =>
-
setValidate(
-
validate() === true ? false
-
: validate() === false ? undefined
-
: true,
-
)
-
}
-
>
-
<Tooltip text={getValidateLabel()}>
-
<span class={`iconify ${getValidateIcon()}`}></span>
-
</Tooltip>
-
<span>Validate</span>
-
</button>
-
<Show when={!props.create && hasUserScope("create") && hasUserScope("delete")}>
-
<Button onClick={() => editRecord(true)}>Recreate</Button>
-
</Show>
-
<Button
-
onClick={() =>
-
props.create ? createRecord(new FormData(formRef)) : editRecord()
-
}
-
>
-
{props.create ? "Create" : "Edit"}
</Button>
</div>
</div>
···
import { addNotification, removeNotification } from "../notification.jsx";
import { TextInput } from "../text-input.jsx";
import Tooltip from "../tooltip.jsx";
+
import { ConfirmSubmit } from "./confirm-submit";
import { FileUpload } from "./file-upload";
import { HandleInput } from "./handle-input";
import { MenuItem } from "./menu-item";
···
const [openUpload, setOpenUpload] = createSignal(false);
const [openInsertMenu, setOpenInsertMenu] = createSignal(false);
const [openHandleDialog, setOpenHandleDialog] = createSignal(false);
+
const [openConfirmDialog, setOpenConfirmDialog] = createSignal(false);
const [isMaximized, setIsMaximized] = createSignal(false);
const [isMinimized, setIsMinimized] = createSignal(false);
const [collectionError, setCollectionError] = createSignal("");
···
};
};
createEffect(() => {
if (openDialog()) {
setCollectionError("");
setRkeyError("");
}
});
+
const createRecord = async (validate: boolean | undefined) => {
+
const formData = new FormData(formRef);
const repo = formData.get("repo")?.toString();
if (!repo) return;
const rpc = new Client({ handler: new OAuthUserAgent(await getSession(repo as Did)) });
···
collection: collection ? collection.toString() : record.$type,
rkey: rkey?.toString().length ? rkey?.toString() : undefined,
record: record,
+
validate: validate,
},
});
if (!res.ok) {
setNotice(`${res.data.error}: ${res.data.message}`);
return;
}
+
setOpenConfirmDialog(false);
setOpenDialog(false);
const id = addNotification({
message: "Record created",
···
navigate(`/${res.data.uri}`);
};
+
const editRecord = async (validate: boolean | undefined, recreate: boolean) => {
const record = editorInstance.view.state.doc.toString();
if (!record) return;
const rpc = new Client({ handler: agent()! });
···
const res = await rpc.post("com.atproto.repo.applyWrites", {
input: {
repo: agent()!.sub,
+
validate: validate,
writes: [
{
collection: params.collection as `${string}.${string}.${string}`,
···
const res = await rpc.post("com.atproto.repo.applyWrites", {
input: {
repo: agent()!.sub,
+
validate: validate,
writes: [
{
collection: params.collection as `${string}.${string}.${string}`,
···
return;
}
}
+
setOpenConfirmDialog(false);
setOpenDialog(false);
const id = addNotification({
message: "Record edited",
···
>
<HandleInput onClose={() => setOpenHandleDialog(false)} />
</Modal>
+
<Modal
+
open={openConfirmDialog()}
+
onClose={() => setOpenConfirmDialog(false)}
+
closeOnClick={false}
+
>
+
<ConfirmSubmit
+
isCreate={props.create}
+
onConfirm={(validate, recreate) => {
+
if (props.create) {
+
createRecord(validate);
+
} else {
+
editRecord(validate, recreate);
+
}
+
}}
+
onClose={() => setOpenConfirmDialog(false)}
+
/>
+
</Modal>
<div class="flex items-center justify-end gap-2">
+
<Button onClick={() => setOpenConfirmDialog(true)}>
+
{props.create ? "Create..." : "Edit..."}
</Button>
</div>
</div>
+2 -2
src/components/create/confirm-submit.tsx
···
return (
<div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] w-[24rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
-
<h2 class="mb-3 font-semibold">{props.isCreate ? "Create" : "Edit"} record</h2>
<div class="flex flex-col gap-3 text-sm">
<div class="flex flex-col gap-1.5">
<div class="flex items-center gap-2">
<button
···
</p>
</div>
</Show>
-
<div class="mt-1 flex justify-between gap-2">
<Button onClick={props.onClose}>Cancel</Button>
<Button
onClick={() => props.onConfirm(validate(), recreate())}
···
return (
<div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] w-[24rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
<div class="flex flex-col gap-3 text-sm">
+
<h2 class="font-semibold">{props.isCreate ? "Create" : "Edit"} record</h2>
<div class="flex flex-col gap-1.5">
<div class="flex items-center gap-2">
<button
···
</p>
</div>
</Show>
+
<div class="flex justify-between gap-2">
<Button onClick={props.onClose}>Cancel</Button>
<Button
onClick={() => props.onConfirm(validate(), recreate())}
+2 -2
package.json
···
"prettier-plugin-tailwindcss": "^0.7.2",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
-
"vite": "^7.2.7",
"vite-plugin-solid": "^2.11.10"
},
"dependencies": {
···
"@atcute/repo": "^0.1.0",
"@atcute/tangled": "^1.0.13",
"@atcute/tid": "^1.0.3",
-
"@codemirror/commands": "^6.10.0",
"@codemirror/lang-json": "^6.0.2",
"@codemirror/lint": "^6.9.2",
"@codemirror/state": "^6.5.2",
···
"prettier-plugin-tailwindcss": "^0.7.2",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
+
"vite": "^7.3.0",
"vite-plugin-solid": "^2.11.10"
},
"dependencies": {
···
"@atcute/repo": "^0.1.0",
"@atcute/tangled": "^1.0.13",
"@atcute/tid": "^1.0.3",
+
"@codemirror/commands": "^6.10.1",
"@codemirror/lang-json": "^6.0.2",
"@codemirror/lint": "^6.9.2",
"@codemirror/state": "^6.5.2",
+240 -240
pnpm-lock.yaml
···
specifier: ^1.0.3
version: 1.0.3
'@codemirror/commands':
-
specifier: ^6.10.0
-
version: 6.10.0
'@codemirror/lang-json':
specifier: ^6.0.2
version: 6.0.2
···
version: 1.2.0(tailwindcss@4.1.18)
'@tailwindcss/vite':
specifier: ^4.1.18
-
version: 4.1.18(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))
prettier:
specifier: ^3.7.4
version: 3.7.4
···
specifier: ^5.9.3
version: 5.9.3
vite:
-
specifier: ^7.2.7
-
version: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
vite-plugin-solid:
specifier: ^2.11.10
-
version: 2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))
packages:
···
'@codemirror/autocomplete@6.20.0':
resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==}
-
'@codemirror/commands@6.10.0':
-
resolution: {integrity: sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==}
'@codemirror/lang-json@6.0.2':
resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==}
···
cpu: [ppc64]
os: [aix]
-
'@esbuild/aix-ppc64@0.25.12':
-
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
···
cpu: [arm64]
os: [android]
-
'@esbuild/android-arm64@0.25.12':
-
resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
···
cpu: [arm]
os: [android]
-
'@esbuild/android-arm@0.25.12':
-
resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
···
cpu: [x64]
os: [android]
-
'@esbuild/android-x64@0.25.12':
-
resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
···
cpu: [arm64]
os: [darwin]
-
'@esbuild/darwin-arm64@0.25.12':
-
resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
···
cpu: [x64]
os: [darwin]
-
'@esbuild/darwin-x64@0.25.12':
-
resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
···
cpu: [arm64]
os: [freebsd]
-
'@esbuild/freebsd-arm64@0.25.12':
-
resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
···
cpu: [x64]
os: [freebsd]
-
'@esbuild/freebsd-x64@0.25.12':
-
resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
···
cpu: [arm64]
os: [linux]
-
'@esbuild/linux-arm64@0.25.12':
-
resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
···
cpu: [arm]
os: [linux]
-
'@esbuild/linux-arm@0.25.12':
-
resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
···
cpu: [ia32]
os: [linux]
-
'@esbuild/linux-ia32@0.25.12':
-
resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
···
cpu: [loong64]
os: [linux]
-
'@esbuild/linux-loong64@0.25.12':
-
resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
···
cpu: [mips64el]
os: [linux]
-
'@esbuild/linux-mips64el@0.25.12':
-
resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
···
cpu: [ppc64]
os: [linux]
-
'@esbuild/linux-ppc64@0.25.12':
-
resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
···
cpu: [riscv64]
os: [linux]
-
'@esbuild/linux-riscv64@0.25.12':
-
resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
···
cpu: [s390x]
os: [linux]
-
'@esbuild/linux-s390x@0.25.12':
-
resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
···
cpu: [x64]
os: [linux]
-
'@esbuild/linux-x64@0.25.12':
-
resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
-
'@esbuild/netbsd-arm64@0.25.12':
-
resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
···
cpu: [x64]
os: [netbsd]
-
'@esbuild/netbsd-x64@0.25.12':
-
resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
···
cpu: [arm64]
os: [openbsd]
-
'@esbuild/openbsd-arm64@0.25.12':
-
resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
···
cpu: [x64]
os: [openbsd]
-
'@esbuild/openbsd-x64@0.25.12':
-
resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
-
'@esbuild/openharmony-arm64@0.25.12':
-
resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
···
cpu: [x64]
os: [sunos]
-
'@esbuild/sunos-x64@0.25.12':
-
resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
···
cpu: [arm64]
os: [win32]
-
'@esbuild/win32-arm64@0.25.12':
-
resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
···
cpu: [ia32]
os: [win32]
-
'@esbuild/win32-ia32@0.25.12':
-
resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
···
cpu: [x64]
os: [win32]
-
'@esbuild/win32-x64@0.25.12':
-
resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
···
peerDependencies:
tailwindcss: '>= 4.0.0'
-
'@iconify/tools@5.0.0':
-
resolution: {integrity: sha512-GY/FsuNdWA/FbkLqgQ8b1PHFkNvjMeSFWaVJdLldYGHBp0lZ64HJlcS0qzLfglacHTd8zYdfQjF74RxGqyGMgw==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
···
'@noble/secp256k1@3.0.0':
resolution: {integrity: sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==}
-
'@rollup/rollup-android-arm-eabi@4.53.3':
-
resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==}
cpu: [arm]
os: [android]
-
'@rollup/rollup-android-arm64@4.53.3':
-
resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==}
cpu: [arm64]
os: [android]
-
'@rollup/rollup-darwin-arm64@4.53.3':
-
resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==}
cpu: [arm64]
os: [darwin]
-
'@rollup/rollup-darwin-x64@4.53.3':
-
resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==}
cpu: [x64]
os: [darwin]
-
'@rollup/rollup-freebsd-arm64@4.53.3':
-
resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==}
cpu: [arm64]
os: [freebsd]
-
'@rollup/rollup-freebsd-x64@4.53.3':
-
resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==}
cpu: [x64]
os: [freebsd]
-
'@rollup/rollup-linux-arm-gnueabihf@4.53.3':
-
resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==}
cpu: [arm]
os: [linux]
-
'@rollup/rollup-linux-arm-musleabihf@4.53.3':
-
resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==}
cpu: [arm]
os: [linux]
-
'@rollup/rollup-linux-arm64-gnu@4.53.3':
-
resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==}
cpu: [arm64]
os: [linux]
-
'@rollup/rollup-linux-arm64-musl@4.53.3':
-
resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==}
cpu: [arm64]
os: [linux]
-
'@rollup/rollup-linux-loong64-gnu@4.53.3':
-
resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==}
cpu: [loong64]
os: [linux]
-
'@rollup/rollup-linux-ppc64-gnu@4.53.3':
-
resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==}
cpu: [ppc64]
os: [linux]
-
'@rollup/rollup-linux-riscv64-gnu@4.53.3':
-
resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==}
cpu: [riscv64]
os: [linux]
-
'@rollup/rollup-linux-riscv64-musl@4.53.3':
-
resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==}
cpu: [riscv64]
os: [linux]
-
'@rollup/rollup-linux-s390x-gnu@4.53.3':
-
resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==}
cpu: [s390x]
os: [linux]
-
'@rollup/rollup-linux-x64-gnu@4.53.3':
-
resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==}
cpu: [x64]
os: [linux]
-
'@rollup/rollup-linux-x64-musl@4.53.3':
-
resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==}
cpu: [x64]
os: [linux]
-
'@rollup/rollup-openharmony-arm64@4.53.3':
-
resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==}
cpu: [arm64]
os: [openharmony]
-
'@rollup/rollup-win32-arm64-msvc@4.53.3':
-
resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==}
cpu: [arm64]
os: [win32]
-
'@rollup/rollup-win32-ia32-msvc@4.53.3':
-
resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==}
cpu: [ia32]
os: [win32]
-
'@rollup/rollup-win32-x64-gnu@4.53.3':
-
resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==}
cpu: [x64]
os: [win32]
-
'@rollup/rollup-win32-x64-msvc@4.53.3':
-
resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==}
cpu: [x64]
os: [win32]
···
peerDependencies:
solid-js: ^1.8.6
-
'@standard-schema/spec@1.0.0':
-
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
'@tailwindcss/node@4.1.18':
resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
···
solid-js:
optional: true
-
baseline-browser-mapping@2.9.7:
-
resolution: {integrity: sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==}
hasBin: true
boolbase@1.0.0:
···
engines: {node: '>=18'}
hasBin: true
-
esbuild@0.25.12:
-
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
engines: {node: '>=18'}
hasBin: true
···
mlly@1.8.0:
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
-
modern-tar@0.7.2:
-
resolution: {integrity: sha512-TGG1ZRk1TAQ3neuZwahAHke3rKsSlro+ooMYtjh9sl2gGPVMLMuWiHgwC7im9T5bSM566RSo2Dko56ETgEvZcA==}
engines: {node: '>=18.0.0'}
ms@2.1.3:
···
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
-
rollup@4.53.3:
-
resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
···
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
-
update-browserslist-db@1.2.2:
-
resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
···
'@testing-library/jest-dom':
optional: true
-
vite@7.2.7:
-
resolution: {integrity: sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
···
'@atcute/lexicons@1.2.5':
dependencies:
-
'@standard-schema/spec': 1.0.0
esm-env: 1.2.2
'@atcute/mst@0.1.0':
···
'@codemirror/view': 6.39.4
'@lezer/common': 1.4.0
-
'@codemirror/commands@6.10.0':
dependencies:
'@codemirror/language': 6.11.3
'@codemirror/state': 6.5.2
···
'@esbuild/aix-ppc64@0.23.1':
optional: true
-
'@esbuild/aix-ppc64@0.25.12':
optional: true
'@esbuild/android-arm64@0.23.1':
optional: true
-
'@esbuild/android-arm64@0.25.12':
optional: true
'@esbuild/android-arm@0.23.1':
optional: true
-
'@esbuild/android-arm@0.25.12':
optional: true
'@esbuild/android-x64@0.23.1':
optional: true
-
'@esbuild/android-x64@0.25.12':
optional: true
'@esbuild/darwin-arm64@0.23.1':
optional: true
-
'@esbuild/darwin-arm64@0.25.12':
optional: true
'@esbuild/darwin-x64@0.23.1':
optional: true
-
'@esbuild/darwin-x64@0.25.12':
optional: true
'@esbuild/freebsd-arm64@0.23.1':
optional: true
-
'@esbuild/freebsd-arm64@0.25.12':
optional: true
'@esbuild/freebsd-x64@0.23.1':
optional: true
-
'@esbuild/freebsd-x64@0.25.12':
optional: true
'@esbuild/linux-arm64@0.23.1':
optional: true
-
'@esbuild/linux-arm64@0.25.12':
optional: true
'@esbuild/linux-arm@0.23.1':
optional: true
-
'@esbuild/linux-arm@0.25.12':
optional: true
'@esbuild/linux-ia32@0.23.1':
optional: true
-
'@esbuild/linux-ia32@0.25.12':
optional: true
'@esbuild/linux-loong64@0.23.1':
optional: true
-
'@esbuild/linux-loong64@0.25.12':
optional: true
'@esbuild/linux-mips64el@0.23.1':
optional: true
-
'@esbuild/linux-mips64el@0.25.12':
optional: true
'@esbuild/linux-ppc64@0.23.1':
optional: true
-
'@esbuild/linux-ppc64@0.25.12':
optional: true
'@esbuild/linux-riscv64@0.23.1':
optional: true
-
'@esbuild/linux-riscv64@0.25.12':
optional: true
'@esbuild/linux-s390x@0.23.1':
optional: true
-
'@esbuild/linux-s390x@0.25.12':
optional: true
'@esbuild/linux-x64@0.23.1':
optional: true
-
'@esbuild/linux-x64@0.25.12':
optional: true
-
'@esbuild/netbsd-arm64@0.25.12':
optional: true
'@esbuild/netbsd-x64@0.23.1':
optional: true
-
'@esbuild/netbsd-x64@0.25.12':
optional: true
'@esbuild/openbsd-arm64@0.23.1':
optional: true
-
'@esbuild/openbsd-arm64@0.25.12':
optional: true
'@esbuild/openbsd-x64@0.23.1':
optional: true
-
'@esbuild/openbsd-x64@0.25.12':
optional: true
-
'@esbuild/openharmony-arm64@0.25.12':
optional: true
'@esbuild/sunos-x64@0.23.1':
optional: true
-
'@esbuild/sunos-x64@0.25.12':
optional: true
'@esbuild/win32-arm64@0.23.1':
optional: true
-
'@esbuild/win32-arm64@0.25.12':
optional: true
'@esbuild/win32-ia32@0.23.1':
optional: true
-
'@esbuild/win32-ia32@0.25.12':
optional: true
'@esbuild/win32-x64@0.23.1':
optional: true
-
'@esbuild/win32-x64@0.25.12':
optional: true
'@fsegurai/codemirror-theme-basic-dark@6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)(@lezer/highlight@1.2.3)':
···
'@iconify/tailwind4@1.2.0(tailwindcss@4.1.18)':
dependencies:
-
'@iconify/tools': 5.0.0
'@iconify/types': 2.0.0
'@iconify/utils': 3.1.0
tailwindcss: 4.1.18
-
'@iconify/tools@5.0.0':
dependencies:
'@cyberalien/svg-utils': 1.0.11
'@iconify/types': 2.0.0
'@iconify/utils': 3.1.0
fflate: 0.8.2
-
modern-tar: 0.7.2
pathe: 2.0.3
svgo: 4.0.0
···
'@noble/secp256k1@3.0.0': {}
-
'@rollup/rollup-android-arm-eabi@4.53.3':
optional: true
-
'@rollup/rollup-android-arm64@4.53.3':
optional: true
-
'@rollup/rollup-darwin-arm64@4.53.3':
optional: true
-
'@rollup/rollup-darwin-x64@4.53.3':
optional: true
-
'@rollup/rollup-freebsd-arm64@4.53.3':
optional: true
-
'@rollup/rollup-freebsd-x64@4.53.3':
optional: true
-
'@rollup/rollup-linux-arm-gnueabihf@4.53.3':
optional: true
-
'@rollup/rollup-linux-arm-musleabihf@4.53.3':
optional: true
-
'@rollup/rollup-linux-arm64-gnu@4.53.3':
optional: true
-
'@rollup/rollup-linux-arm64-musl@4.53.3':
optional: true
-
'@rollup/rollup-linux-loong64-gnu@4.53.3':
optional: true
-
'@rollup/rollup-linux-ppc64-gnu@4.53.3':
optional: true
-
'@rollup/rollup-linux-riscv64-gnu@4.53.3':
optional: true
-
'@rollup/rollup-linux-riscv64-musl@4.53.3':
optional: true
-
'@rollup/rollup-linux-s390x-gnu@4.53.3':
optional: true
-
'@rollup/rollup-linux-x64-gnu@4.53.3':
optional: true
-
'@rollup/rollup-linux-x64-musl@4.53.3':
optional: true
-
'@rollup/rollup-openharmony-arm64@4.53.3':
optional: true
-
'@rollup/rollup-win32-arm64-msvc@4.53.3':
optional: true
-
'@rollup/rollup-win32-ia32-msvc@4.53.3':
optional: true
-
'@rollup/rollup-win32-x64-gnu@4.53.3':
optional: true
-
'@rollup/rollup-win32-x64-msvc@4.53.3':
optional: true
'@skyware/firehose@0.5.2':
···
dependencies:
solid-js: 1.9.10
-
'@standard-schema/spec@1.0.0': {}
'@tailwindcss/node@4.1.18':
dependencies:
···
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
'@tailwindcss/oxide-win32-x64-msvc': 4.1.18
-
'@tailwindcss/vite@4.1.18(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))':
dependencies:
'@tailwindcss/node': 4.1.18
'@tailwindcss/oxide': 4.1.18
tailwindcss: 4.1.18
-
vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
'@types/babel__core@7.20.5':
dependencies:
···
optionalDependencies:
solid-js: 1.9.10
-
baseline-browser-mapping@2.9.7: {}
boolbase@1.0.0: {}
browserslist@4.28.1:
dependencies:
-
baseline-browser-mapping: 2.9.7
caniuse-lite: 1.0.30001760
electron-to-chromium: 1.5.267
node-releases: 2.0.27
-
update-browserslist-db: 1.2.2(browserslist@4.28.1)
caniuse-lite@1.0.30001760: {}
codemirror@6.0.2:
dependencies:
'@codemirror/autocomplete': 6.20.0
-
'@codemirror/commands': 6.10.0
'@codemirror/language': 6.11.3
'@codemirror/lint': 6.9.2
'@codemirror/search': 6.5.11
···
'@esbuild/win32-x64': 0.23.1
optional: true
-
esbuild@0.25.12:
optionalDependencies:
-
'@esbuild/aix-ppc64': 0.25.12
-
'@esbuild/android-arm': 0.25.12
-
'@esbuild/android-arm64': 0.25.12
-
'@esbuild/android-x64': 0.25.12
-
'@esbuild/darwin-arm64': 0.25.12
-
'@esbuild/darwin-x64': 0.25.12
-
'@esbuild/freebsd-arm64': 0.25.12
-
'@esbuild/freebsd-x64': 0.25.12
-
'@esbuild/linux-arm': 0.25.12
-
'@esbuild/linux-arm64': 0.25.12
-
'@esbuild/linux-ia32': 0.25.12
-
'@esbuild/linux-loong64': 0.25.12
-
'@esbuild/linux-mips64el': 0.25.12
-
'@esbuild/linux-ppc64': 0.25.12
-
'@esbuild/linux-riscv64': 0.25.12
-
'@esbuild/linux-s390x': 0.25.12
-
'@esbuild/linux-x64': 0.25.12
-
'@esbuild/netbsd-arm64': 0.25.12
-
'@esbuild/netbsd-x64': 0.25.12
-
'@esbuild/openbsd-arm64': 0.25.12
-
'@esbuild/openbsd-x64': 0.25.12
-
'@esbuild/openharmony-arm64': 0.25.12
-
'@esbuild/sunos-x64': 0.25.12
-
'@esbuild/win32-arm64': 0.25.12
-
'@esbuild/win32-ia32': 0.25.12
-
'@esbuild/win32-x64': 0.25.12
escalade@3.2.0: {}
···
pkg-types: 1.3.1
ufo: 1.6.1
-
modern-tar@0.7.2: {}
ms@2.1.3: {}
···
resolve-pkg-maps@1.0.0:
optional: true
-
rollup@4.53.3:
dependencies:
'@types/estree': 1.0.8
optionalDependencies:
-
'@rollup/rollup-android-arm-eabi': 4.53.3
-
'@rollup/rollup-android-arm64': 4.53.3
-
'@rollup/rollup-darwin-arm64': 4.53.3
-
'@rollup/rollup-darwin-x64': 4.53.3
-
'@rollup/rollup-freebsd-arm64': 4.53.3
-
'@rollup/rollup-freebsd-x64': 4.53.3
-
'@rollup/rollup-linux-arm-gnueabihf': 4.53.3
-
'@rollup/rollup-linux-arm-musleabihf': 4.53.3
-
'@rollup/rollup-linux-arm64-gnu': 4.53.3
-
'@rollup/rollup-linux-arm64-musl': 4.53.3
-
'@rollup/rollup-linux-loong64-gnu': 4.53.3
-
'@rollup/rollup-linux-ppc64-gnu': 4.53.3
-
'@rollup/rollup-linux-riscv64-gnu': 4.53.3
-
'@rollup/rollup-linux-riscv64-musl': 4.53.3
-
'@rollup/rollup-linux-s390x-gnu': 4.53.3
-
'@rollup/rollup-linux-x64-gnu': 4.53.3
-
'@rollup/rollup-linux-x64-musl': 4.53.3
-
'@rollup/rollup-openharmony-arm64': 4.53.3
-
'@rollup/rollup-win32-arm64-msvc': 4.53.3
-
'@rollup/rollup-win32-ia32-msvc': 4.53.3
-
'@rollup/rollup-win32-x64-gnu': 4.53.3
-
'@rollup/rollup-win32-x64-msvc': 4.53.3
fsevents: 2.3.3
sax@1.4.3: {}
···
undici-types@7.16.0:
optional: true
-
update-browserslist-db@1.2.2(browserslist@4.28.1):
dependencies:
browserslist: 4.28.1
escalade: 3.2.0
picocolors: 1.1.1
-
vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)):
dependencies:
'@babel/core': 7.28.5
'@types/babel__core': 7.20.5
···
merge-anything: 5.1.7
solid-js: 1.9.10
solid-refresh: 0.6.3(solid-js@1.9.10)
-
vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
-
vitefu: 1.1.1(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))
transitivePeerDependencies:
- supports-color
-
vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2):
dependencies:
-
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
-
rollup: 4.53.3
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 24.10.1
···
lightningcss: 1.30.2
tsx: 4.19.2
-
vitefu@1.1.1(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)):
optionalDependencies:
-
vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
w3c-keyname@2.2.8: {}
···
specifier: ^1.0.3
version: 1.0.3
'@codemirror/commands':
+
specifier: ^6.10.1
+
version: 6.10.1
'@codemirror/lang-json':
specifier: ^6.0.2
version: 6.0.2
···
version: 1.2.0(tailwindcss@4.1.18)
'@tailwindcss/vite':
specifier: ^4.1.18
+
version: 4.1.18(vite@7.3.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))
prettier:
specifier: ^3.7.4
version: 3.7.4
···
specifier: ^5.9.3
version: 5.9.3
vite:
+
specifier: ^7.3.0
+
version: 7.3.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
vite-plugin-solid:
specifier: ^2.11.10
+
version: 2.11.10(solid-js@1.9.10)(vite@7.3.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))
packages:
···
'@codemirror/autocomplete@6.20.0':
resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==}
+
'@codemirror/commands@6.10.1':
+
resolution: {integrity: sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==}
'@codemirror/lang-json@6.0.2':
resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==}
···
cpu: [ppc64]
os: [aix]
+
'@esbuild/aix-ppc64@0.27.2':
+
resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
···
cpu: [arm64]
os: [android]
+
'@esbuild/android-arm64@0.27.2':
+
resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
···
cpu: [arm]
os: [android]
+
'@esbuild/android-arm@0.27.2':
+
resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
···
cpu: [x64]
os: [android]
+
'@esbuild/android-x64@0.27.2':
+
resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
···
cpu: [arm64]
os: [darwin]
+
'@esbuild/darwin-arm64@0.27.2':
+
resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
···
cpu: [x64]
os: [darwin]
+
'@esbuild/darwin-x64@0.27.2':
+
resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
···
cpu: [arm64]
os: [freebsd]
+
'@esbuild/freebsd-arm64@0.27.2':
+
resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
···
cpu: [x64]
os: [freebsd]
+
'@esbuild/freebsd-x64@0.27.2':
+
resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
···
cpu: [arm64]
os: [linux]
+
'@esbuild/linux-arm64@0.27.2':
+
resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
···
cpu: [arm]
os: [linux]
+
'@esbuild/linux-arm@0.27.2':
+
resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
···
cpu: [ia32]
os: [linux]
+
'@esbuild/linux-ia32@0.27.2':
+
resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
···
cpu: [loong64]
os: [linux]
+
'@esbuild/linux-loong64@0.27.2':
+
resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
···
cpu: [mips64el]
os: [linux]
+
'@esbuild/linux-mips64el@0.27.2':
+
resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
···
cpu: [ppc64]
os: [linux]
+
'@esbuild/linux-ppc64@0.27.2':
+
resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
···
cpu: [riscv64]
os: [linux]
+
'@esbuild/linux-riscv64@0.27.2':
+
resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
···
cpu: [s390x]
os: [linux]
+
'@esbuild/linux-s390x@0.27.2':
+
resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
···
cpu: [x64]
os: [linux]
+
'@esbuild/linux-x64@0.27.2':
+
resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
+
'@esbuild/netbsd-arm64@0.27.2':
+
resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
···
cpu: [x64]
os: [netbsd]
+
'@esbuild/netbsd-x64@0.27.2':
+
resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
···
cpu: [arm64]
os: [openbsd]
+
'@esbuild/openbsd-arm64@0.27.2':
+
resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
···
cpu: [x64]
os: [openbsd]
+
'@esbuild/openbsd-x64@0.27.2':
+
resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
+
'@esbuild/openharmony-arm64@0.27.2':
+
resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
···
cpu: [x64]
os: [sunos]
+
'@esbuild/sunos-x64@0.27.2':
+
resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
···
cpu: [arm64]
os: [win32]
+
'@esbuild/win32-arm64@0.27.2':
+
resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
···
cpu: [ia32]
os: [win32]
+
'@esbuild/win32-ia32@0.27.2':
+
resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
···
cpu: [x64]
os: [win32]
+
'@esbuild/win32-x64@0.27.2':
+
resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
···
peerDependencies:
tailwindcss: '>= 4.0.0'
+
'@iconify/tools@5.0.1':
+
resolution: {integrity: sha512-/znhBN9WIpJd9UtKhyEDfRKwNo8rrOy8dShF8bwSZ1i27ukTSHjeS6bmVK4tTYBYriwFhBf70JT6g8GIRwFvbw==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
···
'@noble/secp256k1@3.0.0':
resolution: {integrity: sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==}
+
'@rollup/rollup-android-arm-eabi@4.53.5':
+
resolution: {integrity: sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==}
cpu: [arm]
os: [android]
+
'@rollup/rollup-android-arm64@4.53.5':
+
resolution: {integrity: sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==}
cpu: [arm64]
os: [android]
+
'@rollup/rollup-darwin-arm64@4.53.5':
+
resolution: {integrity: sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==}
cpu: [arm64]
os: [darwin]
+
'@rollup/rollup-darwin-x64@4.53.5':
+
resolution: {integrity: sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==}
cpu: [x64]
os: [darwin]
+
'@rollup/rollup-freebsd-arm64@4.53.5':
+
resolution: {integrity: sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==}
cpu: [arm64]
os: [freebsd]
+
'@rollup/rollup-freebsd-x64@4.53.5':
+
resolution: {integrity: sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==}
cpu: [x64]
os: [freebsd]
+
'@rollup/rollup-linux-arm-gnueabihf@4.53.5':
+
resolution: {integrity: sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==}
cpu: [arm]
os: [linux]
+
'@rollup/rollup-linux-arm-musleabihf@4.53.5':
+
resolution: {integrity: sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==}
cpu: [arm]
os: [linux]
+
'@rollup/rollup-linux-arm64-gnu@4.53.5':
+
resolution: {integrity: sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==}
cpu: [arm64]
os: [linux]
+
'@rollup/rollup-linux-arm64-musl@4.53.5':
+
resolution: {integrity: sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==}
cpu: [arm64]
os: [linux]
+
'@rollup/rollup-linux-loong64-gnu@4.53.5':
+
resolution: {integrity: sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==}
cpu: [loong64]
os: [linux]
+
'@rollup/rollup-linux-ppc64-gnu@4.53.5':
+
resolution: {integrity: sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==}
cpu: [ppc64]
os: [linux]
+
'@rollup/rollup-linux-riscv64-gnu@4.53.5':
+
resolution: {integrity: sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==}
cpu: [riscv64]
os: [linux]
+
'@rollup/rollup-linux-riscv64-musl@4.53.5':
+
resolution: {integrity: sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==}
cpu: [riscv64]
os: [linux]
+
'@rollup/rollup-linux-s390x-gnu@4.53.5':
+
resolution: {integrity: sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==}
cpu: [s390x]
os: [linux]
+
'@rollup/rollup-linux-x64-gnu@4.53.5':
+
resolution: {integrity: sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==}
cpu: [x64]
os: [linux]
+
'@rollup/rollup-linux-x64-musl@4.53.5':
+
resolution: {integrity: sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==}
cpu: [x64]
os: [linux]
+
'@rollup/rollup-openharmony-arm64@4.53.5':
+
resolution: {integrity: sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==}
cpu: [arm64]
os: [openharmony]
+
'@rollup/rollup-win32-arm64-msvc@4.53.5':
+
resolution: {integrity: sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==}
cpu: [arm64]
os: [win32]
+
'@rollup/rollup-win32-ia32-msvc@4.53.5':
+
resolution: {integrity: sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==}
cpu: [ia32]
os: [win32]
+
'@rollup/rollup-win32-x64-gnu@4.53.5':
+
resolution: {integrity: sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==}
cpu: [x64]
os: [win32]
+
'@rollup/rollup-win32-x64-msvc@4.53.5':
+
resolution: {integrity: sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==}
cpu: [x64]
os: [win32]
···
peerDependencies:
solid-js: ^1.8.6
+
'@standard-schema/spec@1.1.0':
+
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@tailwindcss/node@4.1.18':
resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
···
solid-js:
optional: true
+
baseline-browser-mapping@2.9.10:
+
resolution: {integrity: sha512-2VIKvDx8Z1a9rTB2eCkdPE5nSe28XnA+qivGnWHoB40hMMt/h1hSz0960Zqsn6ZyxWXUie0EBdElKv8may20AA==}
hasBin: true
boolbase@1.0.0:
···
engines: {node: '>=18'}
hasBin: true
+
esbuild@0.27.2:
+
resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==}
engines: {node: '>=18'}
hasBin: true
···
mlly@1.8.0:
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
+
modern-tar@0.7.3:
+
resolution: {integrity: sha512-4W79zekKGyYU4JXVmB78DOscMFaJth2gGhgfTl2alWE4rNe3nf4N2pqenQ0rEtIewrnD79M687Ouba3YGTLOvg==}
engines: {node: '>=18.0.0'}
ms@2.1.3:
···
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
rollup@4.53.5:
+
resolution: {integrity: sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
···
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+
update-browserslist-db@1.2.3:
+
resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
···
'@testing-library/jest-dom':
optional: true
+
vite@7.3.0:
+
resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
···
'@atcute/lexicons@1.2.5':
dependencies:
+
'@standard-schema/spec': 1.1.0
esm-env: 1.2.2
'@atcute/mst@0.1.0':
···
'@codemirror/view': 6.39.4
'@lezer/common': 1.4.0
+
'@codemirror/commands@6.10.1':
dependencies:
'@codemirror/language': 6.11.3
'@codemirror/state': 6.5.2
···
'@esbuild/aix-ppc64@0.23.1':
optional: true
+
'@esbuild/aix-ppc64@0.27.2':
optional: true
'@esbuild/android-arm64@0.23.1':
optional: true
+
'@esbuild/android-arm64@0.27.2':
optional: true
'@esbuild/android-arm@0.23.1':
optional: true
+
'@esbuild/android-arm@0.27.2':
optional: true
'@esbuild/android-x64@0.23.1':
optional: true
+
'@esbuild/android-x64@0.27.2':
optional: true
'@esbuild/darwin-arm64@0.23.1':
optional: true
+
'@esbuild/darwin-arm64@0.27.2':
optional: true
'@esbuild/darwin-x64@0.23.1':
optional: true
+
'@esbuild/darwin-x64@0.27.2':
optional: true
'@esbuild/freebsd-arm64@0.23.1':
optional: true
+
'@esbuild/freebsd-arm64@0.27.2':
optional: true
'@esbuild/freebsd-x64@0.23.1':
optional: true
+
'@esbuild/freebsd-x64@0.27.2':
optional: true
'@esbuild/linux-arm64@0.23.1':
optional: true
+
'@esbuild/linux-arm64@0.27.2':
optional: true
'@esbuild/linux-arm@0.23.1':
optional: true
+
'@esbuild/linux-arm@0.27.2':
optional: true
'@esbuild/linux-ia32@0.23.1':
optional: true
+
'@esbuild/linux-ia32@0.27.2':
optional: true
'@esbuild/linux-loong64@0.23.1':
optional: true
+
'@esbuild/linux-loong64@0.27.2':
optional: true
'@esbuild/linux-mips64el@0.23.1':
optional: true
+
'@esbuild/linux-mips64el@0.27.2':
optional: true
'@esbuild/linux-ppc64@0.23.1':
optional: true
+
'@esbuild/linux-ppc64@0.27.2':
optional: true
'@esbuild/linux-riscv64@0.23.1':
optional: true
+
'@esbuild/linux-riscv64@0.27.2':
optional: true
'@esbuild/linux-s390x@0.23.1':
optional: true
+
'@esbuild/linux-s390x@0.27.2':
optional: true
'@esbuild/linux-x64@0.23.1':
optional: true
+
'@esbuild/linux-x64@0.27.2':
optional: true
+
'@esbuild/netbsd-arm64@0.27.2':
optional: true
'@esbuild/netbsd-x64@0.23.1':
optional: true
+
'@esbuild/netbsd-x64@0.27.2':
optional: true
'@esbuild/openbsd-arm64@0.23.1':
optional: true
+
'@esbuild/openbsd-arm64@0.27.2':
optional: true
'@esbuild/openbsd-x64@0.23.1':
optional: true
+
'@esbuild/openbsd-x64@0.27.2':
optional: true
+
'@esbuild/openharmony-arm64@0.27.2':
optional: true
'@esbuild/sunos-x64@0.23.1':
optional: true
+
'@esbuild/sunos-x64@0.27.2':
optional: true
'@esbuild/win32-arm64@0.23.1':
optional: true
+
'@esbuild/win32-arm64@0.27.2':
optional: true
'@esbuild/win32-ia32@0.23.1':
optional: true
+
'@esbuild/win32-ia32@0.27.2':
optional: true
'@esbuild/win32-x64@0.23.1':
optional: true
+
'@esbuild/win32-x64@0.27.2':
optional: true
'@fsegurai/codemirror-theme-basic-dark@6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)(@lezer/highlight@1.2.3)':
···
'@iconify/tailwind4@1.2.0(tailwindcss@4.1.18)':
dependencies:
+
'@iconify/tools': 5.0.1
'@iconify/types': 2.0.0
'@iconify/utils': 3.1.0
tailwindcss: 4.1.18
+
'@iconify/tools@5.0.1':
dependencies:
'@cyberalien/svg-utils': 1.0.11
'@iconify/types': 2.0.0
'@iconify/utils': 3.1.0
fflate: 0.8.2
+
modern-tar: 0.7.3
pathe: 2.0.3
svgo: 4.0.0
···
'@noble/secp256k1@3.0.0': {}
+
'@rollup/rollup-android-arm-eabi@4.53.5':
optional: true
+
'@rollup/rollup-android-arm64@4.53.5':
optional: true
+
'@rollup/rollup-darwin-arm64@4.53.5':
optional: true
+
'@rollup/rollup-darwin-x64@4.53.5':
optional: true
+
'@rollup/rollup-freebsd-arm64@4.53.5':
optional: true
+
'@rollup/rollup-freebsd-x64@4.53.5':
optional: true
+
'@rollup/rollup-linux-arm-gnueabihf@4.53.5':
optional: true
+
'@rollup/rollup-linux-arm-musleabihf@4.53.5':
optional: true
+
'@rollup/rollup-linux-arm64-gnu@4.53.5':
optional: true
+
'@rollup/rollup-linux-arm64-musl@4.53.5':
optional: true
+
'@rollup/rollup-linux-loong64-gnu@4.53.5':
optional: true
+
'@rollup/rollup-linux-ppc64-gnu@4.53.5':
optional: true
+
'@rollup/rollup-linux-riscv64-gnu@4.53.5':
optional: true
+
'@rollup/rollup-linux-riscv64-musl@4.53.5':
optional: true
+
'@rollup/rollup-linux-s390x-gnu@4.53.5':
optional: true
+
'@rollup/rollup-linux-x64-gnu@4.53.5':
optional: true
+
'@rollup/rollup-linux-x64-musl@4.53.5':
optional: true
+
'@rollup/rollup-openharmony-arm64@4.53.5':
optional: true
+
'@rollup/rollup-win32-arm64-msvc@4.53.5':
optional: true
+
'@rollup/rollup-win32-ia32-msvc@4.53.5':
optional: true
+
'@rollup/rollup-win32-x64-gnu@4.53.5':
optional: true
+
'@rollup/rollup-win32-x64-msvc@4.53.5':
optional: true
'@skyware/firehose@0.5.2':
···
dependencies:
solid-js: 1.9.10
+
'@standard-schema/spec@1.1.0': {}
'@tailwindcss/node@4.1.18':
dependencies:
···
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
'@tailwindcss/oxide-win32-x64-msvc': 4.1.18
+
'@tailwindcss/vite@4.1.18(vite@7.3.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))':
dependencies:
'@tailwindcss/node': 4.1.18
'@tailwindcss/oxide': 4.1.18
tailwindcss: 4.1.18
+
vite: 7.3.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
'@types/babel__core@7.20.5':
dependencies:
···
optionalDependencies:
solid-js: 1.9.10
+
baseline-browser-mapping@2.9.10: {}
boolbase@1.0.0: {}
browserslist@4.28.1:
dependencies:
+
baseline-browser-mapping: 2.9.10
caniuse-lite: 1.0.30001760
electron-to-chromium: 1.5.267
node-releases: 2.0.27
+
update-browserslist-db: 1.2.3(browserslist@4.28.1)
caniuse-lite@1.0.30001760: {}
codemirror@6.0.2:
dependencies:
'@codemirror/autocomplete': 6.20.0
+
'@codemirror/commands': 6.10.1
'@codemirror/language': 6.11.3
'@codemirror/lint': 6.9.2
'@codemirror/search': 6.5.11
···
'@esbuild/win32-x64': 0.23.1
optional: true
+
esbuild@0.27.2:
optionalDependencies:
+
'@esbuild/aix-ppc64': 0.27.2
+
'@esbuild/android-arm': 0.27.2
+
'@esbuild/android-arm64': 0.27.2
+
'@esbuild/android-x64': 0.27.2
+
'@esbuild/darwin-arm64': 0.27.2
+
'@esbuild/darwin-x64': 0.27.2
+
'@esbuild/freebsd-arm64': 0.27.2
+
'@esbuild/freebsd-x64': 0.27.2
+
'@esbuild/linux-arm': 0.27.2
+
'@esbuild/linux-arm64': 0.27.2
+
'@esbuild/linux-ia32': 0.27.2
+
'@esbuild/linux-loong64': 0.27.2
+
'@esbuild/linux-mips64el': 0.27.2
+
'@esbuild/linux-ppc64': 0.27.2
+
'@esbuild/linux-riscv64': 0.27.2
+
'@esbuild/linux-s390x': 0.27.2
+
'@esbuild/linux-x64': 0.27.2
+
'@esbuild/netbsd-arm64': 0.27.2
+
'@esbuild/netbsd-x64': 0.27.2
+
'@esbuild/openbsd-arm64': 0.27.2
+
'@esbuild/openbsd-x64': 0.27.2
+
'@esbuild/openharmony-arm64': 0.27.2
+
'@esbuild/sunos-x64': 0.27.2
+
'@esbuild/win32-arm64': 0.27.2
+
'@esbuild/win32-ia32': 0.27.2
+
'@esbuild/win32-x64': 0.27.2
escalade@3.2.0: {}
···
pkg-types: 1.3.1
ufo: 1.6.1
+
modern-tar@0.7.3: {}
ms@2.1.3: {}
···
resolve-pkg-maps@1.0.0:
optional: true
+
rollup@4.53.5:
dependencies:
'@types/estree': 1.0.8
optionalDependencies:
+
'@rollup/rollup-android-arm-eabi': 4.53.5
+
'@rollup/rollup-android-arm64': 4.53.5
+
'@rollup/rollup-darwin-arm64': 4.53.5
+
'@rollup/rollup-darwin-x64': 4.53.5
+
'@rollup/rollup-freebsd-arm64': 4.53.5
+
'@rollup/rollup-freebsd-x64': 4.53.5
+
'@rollup/rollup-linux-arm-gnueabihf': 4.53.5
+
'@rollup/rollup-linux-arm-musleabihf': 4.53.5
+
'@rollup/rollup-linux-arm64-gnu': 4.53.5
+
'@rollup/rollup-linux-arm64-musl': 4.53.5
+
'@rollup/rollup-linux-loong64-gnu': 4.53.5
+
'@rollup/rollup-linux-ppc64-gnu': 4.53.5
+
'@rollup/rollup-linux-riscv64-gnu': 4.53.5
+
'@rollup/rollup-linux-riscv64-musl': 4.53.5
+
'@rollup/rollup-linux-s390x-gnu': 4.53.5
+
'@rollup/rollup-linux-x64-gnu': 4.53.5
+
'@rollup/rollup-linux-x64-musl': 4.53.5
+
'@rollup/rollup-openharmony-arm64': 4.53.5
+
'@rollup/rollup-win32-arm64-msvc': 4.53.5
+
'@rollup/rollup-win32-ia32-msvc': 4.53.5
+
'@rollup/rollup-win32-x64-gnu': 4.53.5
+
'@rollup/rollup-win32-x64-msvc': 4.53.5
fsevents: 2.3.3
sax@1.4.3: {}
···
undici-types@7.16.0:
optional: true
+
update-browserslist-db@1.2.3(browserslist@4.28.1):
dependencies:
browserslist: 4.28.1
escalade: 3.2.0
picocolors: 1.1.1
+
vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.3.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)):
dependencies:
'@babel/core': 7.28.5
'@types/babel__core': 7.20.5
···
merge-anything: 5.1.7
solid-js: 1.9.10
solid-refresh: 0.6.3(solid-js@1.9.10)
+
vite: 7.3.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
+
vitefu: 1.1.1(vite@7.3.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))
transitivePeerDependencies:
- supports-color
+
vite@7.3.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2):
dependencies:
+
esbuild: 0.27.2
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
+
rollup: 4.53.5
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 24.10.1
···
lightningcss: 1.30.2
tsx: 4.19.2
+
vitefu@1.1.1(vite@7.3.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)):
optionalDependencies:
+
vite: 7.3.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
w3c-keyname@2.2.8: {}
+1 -1
src/views/logs.tsx
···
</div>
<div class="flex items-center gap-1.5 text-sm font-medium">
<Show when={validLog() === true}>
-
<span class="iconify lucide--check-circle-2 text-green-500 dark:text-green-400"></span>
<span>Valid log</span>
</Show>
<Show when={validLog() === false}>
···
</div>
<div class="flex items-center gap-1.5 text-sm font-medium">
<Show when={validLog() === true}>
+
<span class="iconify lucide--check-circle-2 text-green-600 dark:text-green-400"></span>
<span>Valid log</span>
</Show>
<Show when={validLog() === false}>