diff --git a/web/src/lib/components/repo/issues/IssueForm.stories.svelte b/web/src/lib/components/repo/issues/IssueForm.stories.svelte
new file mode 100644
index 00000000..6f4bdeaf
--- /dev/null
+++ b/web/src/lib/components/repo/issues/IssueForm.stories.svelte
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ {
+ const canvas = within(canvasElement);
+ await userEvent.type(canvas.getByLabelText("Title"), "Something broke");
+ await userEvent.type(
+ canvas.getByPlaceholderText(/describe your issue/i),
+ "details{Control>}{Enter}{/Control}"
+ );
+ await waitFor(() =>
+ expect(canvas.getByRole("alert")).toHaveTextContent(/network request failed/i)
+ );
+ }}
+>
+ {#snippet template(args)}
+
+
+
+ {/snippet}
+
diff --git a/web/src/lib/components/repo/issues/IssueForm.svelte b/web/src/lib/components/repo/issues/IssueForm.svelte
new file mode 100644
index 00000000..3cf301b1
--- /dev/null
+++ b/web/src/lib/components/repo/issues/IssueForm.svelte
@@ -0,0 +1,125 @@
+
+
+
diff --git a/web/src/lib/components/repo/types.ts b/web/src/lib/components/repo/types.ts
index 142dc2eb..0c3d8622 100644
--- a/web/src/lib/components/repo/types.ts
+++ b/web/src/lib/components/repo/types.ts
@@ -38,3 +38,14 @@ export interface LanguageSlice {
percentage: number;
share: number;
}
+
+export interface IssueSummary {
+ uri: string;
+ rkey: string;
+ title: string;
+ state: "open" | "closed";
+ authorHandle: string;
+ authorDid: string;
+ createdAt: string;
+ commentCount: number;
+}
diff --git a/web/src/lib/components/testing/MockAuthProvider.svelte b/web/src/lib/components/testing/MockAuthProvider.svelte
new file mode 100644
index 00000000..21a69a40
--- /dev/null
+++ b/web/src/lib/components/testing/MockAuthProvider.svelte
@@ -0,0 +1,30 @@
+
+
+{@render children()}
diff --git a/web/src/lib/components/ui/MarkdownEditor.stories.svelte b/web/src/lib/components/ui/MarkdownEditor.stories.svelte
new file mode 100644
index 00000000..122c2a3b
--- /dev/null
+++ b/web/src/lib/components/ui/MarkdownEditor.stories.svelte
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/web/src/lib/components/ui/MarkdownEditor.svelte b/web/src/lib/components/ui/MarkdownEditor.svelte
new file mode 100644
index 00000000..8b667208
--- /dev/null
+++ b/web/src/lib/components/ui/MarkdownEditor.svelte
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+ {#if tab === "write"}
+
+ {:else if previewHtml}
+
+
+ {@html previewHtml}
+
+ {:else}
+
+ {previewing ? "Rendering…" : "Nothing to preview."}
+
+ {/if}
+
diff --git a/web/src/lib/components/ui/Textarea.svelte b/web/src/lib/components/ui/Textarea.svelte
index 61f83d3b..4022d9d3 100644
--- a/web/src/lib/components/ui/Textarea.svelte
+++ b/web/src/lib/components/ui/Textarea.svelte
@@ -59,6 +59,7 @@
iconLeft?: Component;
iconRight?: Component;
class?: string;
+ element?: HTMLTextAreaElement;
}
let {
@@ -71,6 +72,7 @@
iconLeft,
iconRight,
class: className,
+ element = $bindable(),
...rest
}: Props = $props();
@@ -83,6 +85,7 @@
{/if}