Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* vi:ft=jsonc */
// NOTE: this file is interpreted as jsonc, but you can't
// use a jsonc file extension or oxlint will ignore it, hence
// the modeline.
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"categories": {
"correctness": "error"
// TODO: un-comment these and fix the issues
// "suspicious": "error",
// "restriction": "error"
},
"plugins": ["eslint", "import", "oxc", "promise", "react", "typescript", "unicorn"],
"ignorePatterns": ["dist", "src/spicedb-common/protodefs", "public"],
"rules": {
// TODO: fix this and get rid of it
"react/only-export-components": [
"warn",
{
"allowConstantExport": true
}
]
}
}
4 changes: 2 additions & 2 deletions api/share.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createHash } from "crypto";

import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import type { VercelRequest, VercelResponse } from "@vercel/node";
import type { VercelRequest, VercelRequestBody, VercelResponse } from "@vercel/node";

const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

Expand Down Expand Up @@ -31,7 +31,7 @@ function computeShareHash(salt: string, data: string): string {
return b64.substring(0, hashLen);
}

function validateSharedDataV2(data): data is SharedDataV2 {
function validateSharedDataV2(data: VercelRequestBody): data is SharedDataV2 {
if (typeof data !== "object" || data === null) {
return false;
}
Expand Down
25 changes: 0 additions & 25 deletions eslint.config.js

This file was deleted.

11 changes: 4 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"dev": "HTTPS=true vite",
"build": "tsc -b && vite build",
"test": "vitest",
"lint": "eslint .",
"lint-fix": "eslint --fix .",
"lint": "oxlint --type-aware --type-check",
"lint-fix": "oxlint --type-aware --type-check --fix",
"typecheck": "tsc --noEmit",
"format": "oxfmt",
"format:check": "oxfmt --check",
Expand Down Expand Up @@ -79,7 +79,6 @@
"zod": "^4.2.1"
},
"devDependencies": {
"@eslint/js": "^9.18.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/d3-scale-chromatic": "^3.0.0",
Expand All @@ -99,14 +98,12 @@
"@vitejs/plugin-react": "^4.3.4",
"cypress": "^12.9.0",
"cypress-wait-until": "^1.7.2",
"eslint": "^9.18.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.18",
"globals": "^15.14.0",
"oxfmt": "^0.28.0",
"oxlint": "^1.48.0",
"oxlint-tsgolint": "^0.14.0",
"tw-animate-css": "^1.2.8",
"typescript": "~5.7.3",
"typescript-eslint": "^8.20.0",
"vite": "^6.0.7",
"vite-plugin-svgr": "^4.3.0",
"vitest": "^2.1.8"
Expand Down
9 changes: 8 additions & 1 deletion src/components/CheckDebugTraceView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ function ContextTreeView(context: JsonObject | undefined) {
}

return (
<TreeItem nodeId="" label={label}>
<TreeItem key={key} nodeId="" label={label}>
{isItemValue ? value : undefined}
</TreeItem>
);
Expand All @@ -280,13 +280,19 @@ function ContextTreeView(context: JsonObject | undefined) {

function ContextTreeValue(value: JsonValue) {
if (value === null) {
// NOTE: i'm not sure why this triggers on array literals.
// oxlint-disable-next-line eslint-plugin-react(jsx-key)
return [<code>null</code>, false];
}
if (typeof value === "boolean") {
// oxlint-disable-next-line eslint-plugin-react(jsx-key)
return [<code>{value.toString()}</code>, false];
}
if (Array.isArray(value)) {
return [
// NOTE: not sure what the key would be in this case. I think I'd rather get rid
// of this code.
// oxlint-disable-next-line eslint-plugin-react(jsx-key)
value.map((v) => {
return <TreeItem nodeId="">{ContextTreeValue(v)}</TreeItem>;
}),
Expand All @@ -298,5 +304,6 @@ function ContextTreeValue(value: JsonValue) {
return [ContextTreeView(value), true];
}
// If we've gotten this far, we have a number or a string and we can render it straight out.
// oxlint-disable-next-line eslint-plugin-react(jsx-key)
return [<code>{value}</code>, false];
}
4 changes: 2 additions & 2 deletions src/components/EditorDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Editor, { DiffEditor, useMonaco } from "@monaco-editor/react";
import { useDebouncedCallback } from "@tanstack/react-pacer/debouncer";
import { useNavigate, useLocation } from "@tanstack/react-router";
import lineColumn from "line-column";
import monaco from "monaco-editor";
import * as monaco from "monaco-editor";
import { useEffect, useMemo, useRef, useState } from "react";
import { flushSync } from "react-dom";

Expand Down Expand Up @@ -163,7 +163,7 @@ export function EditorDisplay(props: EditorDisplayProps) {
// TODO: this shouldn't be necessary. Moving to redux may make this less painful.
const updated = datastore.update(currentItem!, value || "");
if (updated && updated.pathname !== location.pathname) {
navigate({ to: updated.pathname, replace: true });
void navigate({ to: updated.pathname, replace: true });
}

props.datastoreUpdated();
Expand Down
9 changes: 3 additions & 6 deletions src/components/ExamplesDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,9 @@ export function ExamplesDropdown({
const posthog = usePostHog();

useEffect(() => {
const fetchExamples = async () => {
if (examples === undefined) {
setExamples(await LoadExamples());
}
};
fetchExamples();
if (examples === undefined) {
setExamples(LoadExamples());
}
}, [examples]);

return (
Expand Down
75 changes: 29 additions & 46 deletions src/components/FullPlayground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -374,11 +374,9 @@ export function ThemedAppView(props: { datastore: DataStore }) {
// Effect: If the user lands on the `/` route, redirect them to the schema editor.
// TODO: this should probably be a redirect at the routing layer.
useEffect(() => {
(async () => {
if (currentItem === undefined) {
navigate({ to: DataStorePaths.Schema(), replace: true });
}
})();
if (currentItem === undefined) {
void navigate({ to: DataStorePaths.Schema(), replace: true });
}
}, [datastore, currentItem, navigate]);

const conductDownload = () => {
Expand All @@ -389,35 +387,33 @@ export function ThemedAppView(props: { datastore: DataStore }) {
saveAs(blob, `authzed-download-${hash}.yaml`);
};

const conductUpload = () => {
(async () => {
const file = await fileDialog({
multiple: false,
strict: true,
accept: ".yaml",
const conductUpload = async () => {
const file = await fileDialog({
multiple: false,
strict: true,
accept: ".yaml",
});
if (file) {
pushEvent("load-yaml", {
filename: file.name,
});
if (file) {
pushEvent("load-yaml", {
filename: file.name,
});

const contents = await getFileContentsAsText(file);
const uploaded = parseValidationYAML(contents);
if ("message" in uploaded) {
toast.error("Could not load uploaded YAML", {
description: `The uploaded validation YAML is invalid: ${uploaded.message}`,
});
return;
}
const contents = await file.text();
const uploaded = parseValidationYAML(contents);
if ("message" in uploaded) {
toast.error("Could not load uploaded YAML", {
description: `The uploaded validation YAML is invalid: ${uploaded.message}`,
});
return;
}

services.liveCheckService.clear();
services.liveCheckService.clear();

datastore.loadFromParsed(uploaded);
datastoreUpdated();
datastore.loadFromParsed(uploaded);
datastoreUpdated();

navigate({ to: DataStorePaths.Schema(), replace: true });
}
})();
void navigate({ to: DataStorePaths.Schema(), replace: true });
}
};

const formatSchema = () => {
Expand Down Expand Up @@ -518,7 +514,7 @@ export function ThemedAppView(props: { datastore: DataStore }) {
datastoreUpdated();

services.liveCheckService.clear();
navigate({ to: DataStorePaths.Schema(), replace: true });
void navigate({ to: DataStorePaths.Schema(), replace: true });
};

const [previousValidationForDiff, setPreviousValidationForDiff] = useState<string | undefined>(
Expand Down Expand Up @@ -593,19 +589,19 @@ export function ThemedAppView(props: { datastore: DataStore }) {
selectedTabValue: string,
) => {
const item = datastore.getById(selectedTabValue)!;
navigate({ to: item.pathname });
void navigate({ to: item.pathname });
};

const setDismissTour = () => {
setShowTour(false);
setCookie("dismiss-tour", true);
navigate({ to: DataStorePaths.Schema() });
void navigate({ to: DataStorePaths.Schema() });
};

const handleTourBeforeStep = (selector: string) => {
// Activate the Assertions tab before the assertions dialogs
if (selector.includes(TourElementClass.assert)) {
navigate({ to: DataStorePaths.Assertions() });
void navigate({ to: DataStorePaths.Assertions() });
}
};

Expand Down Expand Up @@ -1135,16 +1131,3 @@ function IsolatedEditorDisplay(props: EditorDisplayProps) {

return <EditorDisplay {...props} datastoreUpdated={datastoreUpdated} />;
}

const getFileContentsAsText = async (file: File): Promise<string> => {
return new Promise(
(resolve: (value: string | PromiseLike<string>) => void, reject: () => void) => {
const reader = new FileReader();
reader.onloadend = function (e: ProgressEvent<FileReader>) {
resolve(e.target?.result?.toString() ?? "");
};
reader.onerror = reject;
reader.readAsText(file);
},
);
};
6 changes: 4 additions & 2 deletions src/components/ReadOnlyRelationshipsGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ export function ReadOnlyRelationshipsGrid(props: {
</TableRow>
</TableHead>
<TableBody>
{props.relationships.map((relationship: Relationship) => {
{props.relationships.map((relationship, index) => {
return (
<TableRow>
// NOTE: an index is appropriate here because a user could theoretically
// write a duplicate relationship, and the position makes some sense as a key
<TableRow key={index}>
<TableCell className={classes.def}>
{relationship.resourceAndRelation?.namespace}
</TableCell>
Expand Down
6 changes: 3 additions & 3 deletions src/components/ShareLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function ShareLoader(props: {
setLoadingStatus(SharedLoadingStatus.LOADING);

// Load the shared data.
(async () => {
void (async () => {
const apiEndpoint = AppConfig().shareApiEndpoint;
if (!apiEndpoint) {
return;
Expand All @@ -67,7 +67,7 @@ export function ShareLoader(props: {
// TODO: use routing for this instead of string manipulation
const pieces = location.pathname.replace(urlPrefix, "").split("/");
if (pieces.length < 1 && !props.sharedRequired) {
navigate({ to: "/" });
await navigate({ to: "/" });
return;
}

Expand Down Expand Up @@ -144,7 +144,7 @@ export function ShareLoader(props: {

if (!props.sharedRequired) {
// TODO: do this with routing as well
navigate({
await navigate({
to: location.pathname.slice(0, urlPrefix.length + shareReference.length),
replace: true,
});
Expand Down
5 changes: 4 additions & 1 deletion src/components/panels/errordisplays.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ export function DeveloperErrorDisplay({ error }: { error: DeveloperError }) {
<ul className="">
{error.path.map((item) => (
// NOTE: the \2192 here is the → character; tailwind needs it as an escape sequence.
<li className="after:content-['\2192'] after:ml-2 last:after:content-none" key={item}>
<li
className="after:content-['\u2192'] after:ml-2 last:after:content-none"
key={item}
>
{item}
</li>
))}
Expand Down
9 changes: 5 additions & 4 deletions src/components/panels/problems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import clsx from "clsx";

import TabLabel from "../../playground-ui/TabLabel";
import { DataStorePaths } from "../../services/datastore";
import { RelationshipFound } from "../../spicedb-common/parsing";
import {
DeveloperError,
DeveloperWarning,
Expand Down Expand Up @@ -94,13 +93,15 @@ export function ProblemsPanel({ services }: PanelProps) {
<div className={clsx(classes.apiOutput)}>
{!services.problemService.hasProblems && <span>No problems found</span>}
{services.problemService.invalidRelationships.map(
(invalid: RelationshipFound, index: number) => {
// NOTE: an index is appropriate here because a user could theoretically
// write a duplicate relationship, and the position makes some sense as a key
(invalid, index) => {
if (!("errorMessage" in invalid.parsed)) {
return <div />;
return <div key={index} />;
}

return (
<Paper className={classes.errorContainer} key={`ir${index}`}>
<Paper className={classes.errorContainer} key={index}>
<div>
<div className={classes.validationErrorContext}>
In
Expand Down
Loading