mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
Rename web app to Buzz (#959)
Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
@@ -176,7 +176,7 @@ or invoke with the full path.
|
|||||||
|
|
||||||
### Deep Links
|
### Deep Links
|
||||||
|
|
||||||
`sprout://message?channel=<uuid>&id=<hex>` links reference a specific message
|
`buzz://message?channel=<uuid>&id=<hex>` links reference a specific message
|
||||||
thread. To read the linked thread:
|
thread. To read the linked thread:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ FROM --platform=linux/amd64 node:24-bookworm-slim AS web-builder
|
|||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||||
COPY web/ web/
|
COPY web/ web/
|
||||||
RUN corepack enable && pnpm install --frozen-lockfile --filter sprout-web
|
RUN corepack enable && pnpm install --frozen-lockfile --filter buzz-web
|
||||||
RUN pnpm -C web build
|
RUN pnpm -C web build
|
||||||
|
|
||||||
# ── Runtime stage ───────────────────────────────────────────
|
# ── Runtime stage ───────────────────────────────────────────
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ fn shutdown_managed_agents(app: &tauri::AppHandle) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the query string of a `sprout://message?…` URL into the JSON
|
/// Parse the query string of a `buzz://message?…` URL into the JSON
|
||||||
/// payload emitted on `deep-link-message`. Returns `None` when a required
|
/// payload emitted on `deep-link-message`. Returns `None` when a required
|
||||||
/// param (`channel`, `id`) is missing or empty — mirroring the validation
|
/// param (`channel`, `id`) is missing or empty — mirroring the validation
|
||||||
/// policy of the `connect` arm so the frontend never sees a half-formed
|
/// policy of the `connect` arm so the frontend never sees a half-formed
|
||||||
@@ -278,10 +278,10 @@ fn parse_message_deep_link(url: &Url) -> Option<serde_json::Value> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle an incoming `sprout://` deep link URL.
|
/// Handle an incoming `buzz://` deep link URL, with `sprout://` accepted as a legacy alias.
|
||||||
///
|
///
|
||||||
/// Currently supports:
|
/// Currently supports:
|
||||||
/// - `sprout://connect?relay=<ws(s)://...>` — emits `deep-link-connect` to the frontend
|
/// - `buzz://connect?relay=<ws(s)://...>` — emits `deep-link-connect` to the frontend
|
||||||
fn handle_deep_link_url(app: &tauri::AppHandle, url_str: &str) {
|
fn handle_deep_link_url(app: &tauri::AppHandle, url_str: &str) {
|
||||||
let url = match Url::parse(url_str) {
|
let url = match Url::parse(url_str) {
|
||||||
Ok(u) => u,
|
Ok(u) => u,
|
||||||
@@ -291,8 +291,8 @@ fn handle_deep_link_url(app: &tauri::AppHandle, url_str: &str) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if url.scheme() != "sprout" {
|
if url.scheme() != "sprout" && url.scheme() != "buzz" {
|
||||||
eprintln!("sprout-desktop: ignoring non-sprout deep link: {url_str}");
|
eprintln!("sprout-desktop: ignoring unsupported deep link scheme: {url_str}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,7 +324,7 @@ fn handle_deep_link_url(app: &tauri::AppHandle, url_str: &str) {
|
|||||||
let _ = app.emit("deep-link-connect", relay_url);
|
let _ = app.emit("deep-link-connect", relay_url);
|
||||||
}
|
}
|
||||||
Some("message") => {
|
Some("message") => {
|
||||||
// `sprout://message?channel=<uuid>&id=<eventId>[&thread=<rootId>]`
|
// `buzz://message?channel=<uuid>&id=<eventId>[&thread=<rootId>]`
|
||||||
//
|
//
|
||||||
// Validation policy mirrors the `connect` arm: parse what we
|
// Validation policy mirrors the `connect` arm: parse what we
|
||||||
// need, refuse to emit anything if a required param is missing
|
// need, refuse to emit anything if a required param is missing
|
||||||
@@ -373,7 +373,7 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
// Forward any deep link URLs from the duplicate launch.
|
// Forward any deep link URLs from the duplicate launch.
|
||||||
for arg in &argv {
|
for arg in &argv {
|
||||||
if arg.starts_with("sprout://") {
|
if arg.starts_with("sprout://") || arg.starts_with("buzz://") {
|
||||||
handle_deep_link_url(app, arg);
|
handle_deep_link_url(app, arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -869,6 +869,14 @@ mod tests {
|
|||||||
assert!(payload["threadRootId"].is_null());
|
assert!(payload["threadRootId"].is_null());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_message_deep_link_accepts_buzz_scheme() {
|
||||||
|
let url = Url::parse("buzz://message?channel=abc&id=xyz").unwrap();
|
||||||
|
let payload = parse_message_deep_link(&url).expect("required params present");
|
||||||
|
assert_eq!(payload["channelId"], "abc");
|
||||||
|
assert_eq!(payload["messageId"], "xyz");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_message_deep_link_includes_thread_root() {
|
fn parse_message_deep_link_includes_thread_root() {
|
||||||
let url = Url::parse("sprout://message?channel=abc&id=xyz&thread=root1").unwrap();
|
let url = Url::parse("sprout://message?channel=abc&id=xyz&thread=root1").unwrap();
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
},
|
},
|
||||||
"deep-link": {
|
"deep-link": {
|
||||||
"desktop": {
|
"desktop": {
|
||||||
"schemes": ["sprout"]
|
"schemes": ["sprout", "buzz"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -524,7 +524,7 @@ export function AppShell() {
|
|||||||
unreadChannelIds.size,
|
unreadChannelIds.size,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Dispatch `sprout://message` deep links into the router.
|
// Dispatch `buzz://message` deep links into the router.
|
||||||
useMessageDeepLinks();
|
useMessageDeepLinks();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const THREAD =
|
|||||||
|
|
||||||
test("buildMessageLink → parseMessageLink round-trips without thread", () => {
|
test("buildMessageLink → parseMessageLink round-trips without thread", () => {
|
||||||
const url = buildMessageLink({ channelId: CHANNEL, messageId: MESSAGE });
|
const url = buildMessageLink({ channelId: CHANNEL, messageId: MESSAGE });
|
||||||
assert.equal(url, `sprout://message?channel=${CHANNEL}&id=${MESSAGE}`);
|
assert.equal(url, `buzz://message?channel=${CHANNEL}&id=${MESSAGE}`);
|
||||||
|
|
||||||
const parsed = parseMessageLink(url);
|
const parsed = parseMessageLink(url);
|
||||||
assert.equal(parsed.ok, true);
|
assert.equal(parsed.ok, true);
|
||||||
@@ -52,8 +52,8 @@ test("buildMessageLink treats null/empty thread as absent", () => {
|
|||||||
messageId: MESSAGE,
|
messageId: MESSAGE,
|
||||||
threadRootId: "",
|
threadRootId: "",
|
||||||
});
|
});
|
||||||
assert.equal(a, `sprout://message?channel=${CHANNEL}&id=${MESSAGE}`);
|
assert.equal(a, `buzz://message?channel=${CHANNEL}&id=${MESSAGE}`);
|
||||||
assert.equal(b, `sprout://message?channel=${CHANNEL}&id=${MESSAGE}`);
|
assert.equal(b, `buzz://message?channel=${CHANNEL}&id=${MESSAGE}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("buildMessageLink rejects missing required params", () => {
|
test("buildMessageLink rejects missing required params", () => {
|
||||||
@@ -61,7 +61,7 @@ test("buildMessageLink rejects missing required params", () => {
|
|||||||
assert.throws(() => buildMessageLink({ channelId: CHANNEL, messageId: "" }));
|
assert.throws(() => buildMessageLink({ channelId: CHANNEL, messageId: "" }));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parseMessageLink rejects non-sprout schemes", () => {
|
test("parseMessageLink rejects unsupported schemes", () => {
|
||||||
const r = parseMessageLink(
|
const r = parseMessageLink(
|
||||||
`https://example.com/?channel=${CHANNEL}&id=${MESSAGE}`,
|
`https://example.com/?channel=${CHANNEL}&id=${MESSAGE}`,
|
||||||
);
|
);
|
||||||
@@ -69,20 +69,20 @@ test("parseMessageLink rejects non-sprout schemes", () => {
|
|||||||
assert.equal(r.ok === false && r.reason, "wrong-scheme");
|
assert.equal(r.ok === false && r.reason, "wrong-scheme");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parseMessageLink rejects sprout:// with wrong host", () => {
|
test("parseMessageLink rejects buzz:// with wrong host", () => {
|
||||||
const r = parseMessageLink(`sprout://connect?relay=wss://example.com`);
|
const r = parseMessageLink(`buzz://connect?relay=wss://example.com`);
|
||||||
assert.equal(r.ok, false);
|
assert.equal(r.ok, false);
|
||||||
assert.equal(r.ok === false && r.reason, "wrong-host");
|
assert.equal(r.ok === false && r.reason, "wrong-host");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parseMessageLink rejects missing channel", () => {
|
test("parseMessageLink rejects missing channel", () => {
|
||||||
const r = parseMessageLink(`sprout://message?id=${MESSAGE}`);
|
const r = parseMessageLink(`buzz://message?id=${MESSAGE}`);
|
||||||
assert.equal(r.ok, false);
|
assert.equal(r.ok, false);
|
||||||
assert.equal(r.ok === false && r.reason, "missing-channel");
|
assert.equal(r.ok === false && r.reason, "missing-channel");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parseMessageLink rejects missing id", () => {
|
test("parseMessageLink rejects missing id", () => {
|
||||||
const r = parseMessageLink(`sprout://message?channel=${CHANNEL}`);
|
const r = parseMessageLink(`buzz://message?channel=${CHANNEL}`);
|
||||||
assert.equal(r.ok, false);
|
assert.equal(r.ok, false);
|
||||||
assert.equal(r.ok === false && r.reason, "missing-id");
|
assert.equal(r.ok === false && r.reason, "missing-id");
|
||||||
});
|
});
|
||||||
@@ -93,11 +93,28 @@ test("parseMessageLink rejects malformed URL strings", () => {
|
|||||||
assert.equal(r.ok === false && r.reason, "invalid-url");
|
assert.equal(r.ok === false && r.reason, "invalid-url");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("isMessageLink matches only sprout://message", () => {
|
test("parseMessageLink accepts legacy sprout://message links", () => {
|
||||||
|
const r = parseMessageLink(
|
||||||
|
`sprout://message?channel=${CHANNEL}&id=${MESSAGE}`,
|
||||||
|
);
|
||||||
|
assert.equal(r.ok, true);
|
||||||
|
assert.deepEqual(r.ok && r.value, {
|
||||||
|
channelId: CHANNEL,
|
||||||
|
messageId: MESSAGE,
|
||||||
|
threadRootId: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("isMessageLink matches buzz://message and legacy sprout://message", () => {
|
||||||
|
assert.equal(
|
||||||
|
isMessageLink(`buzz://message?channel=${CHANNEL}&id=${MESSAGE}`),
|
||||||
|
true,
|
||||||
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
isMessageLink(`sprout://message?channel=${CHANNEL}&id=${MESSAGE}`),
|
isMessageLink(`sprout://message?channel=${CHANNEL}&id=${MESSAGE}`),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
assert.equal(isMessageLink("buzz://connect?relay=wss://x"), false);
|
||||||
assert.equal(isMessageLink("sprout://connect?relay=wss://x"), false);
|
assert.equal(isMessageLink("sprout://connect?relay=wss://x"), false);
|
||||||
assert.equal(isMessageLink("https://example.com"), false);
|
assert.equal(isMessageLink("https://example.com"), false);
|
||||||
assert.equal(isMessageLink(undefined), false);
|
assert.equal(isMessageLink(undefined), false);
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* `sprout://message` link encoding for "Copy link" / deep-link-to-message.
|
* `buzz://message` link encoding for "Copy link" / deep-link-to-message.
|
||||||
*
|
*
|
||||||
* Format: `sprout://message?channel=<uuid>&id=<eventId>[&thread=<rootId>]`
|
* Format: `buzz://message?channel=<uuid>&id=<eventId>[&thread=<rootId>]`
|
||||||
*
|
|
||||||
* Mirrors the existing `sprout://connect?relay=…` scheme already registered
|
|
||||||
* in `tauri.conf.json` and handled in `desktop/src-tauri/src/lib.rs`.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const MESSAGE_LINK_SCHEME = "buzz:";
|
||||||
|
const LEGACY_MESSAGE_LINK_SCHEME = "sprout:";
|
||||||
const MESSAGE_LINK_HOST = "message";
|
const MESSAGE_LINK_HOST = "message";
|
||||||
|
|
||||||
export type MessageLinkInput = {
|
export type MessageLinkInput = {
|
||||||
@@ -35,7 +34,7 @@ export type MessageLinkParseResult =
|
|||||||
| { ok: false; reason: string };
|
| { ok: false; reason: string };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a `sprout://message` URL for a given channel + message.
|
* Build a `buzz://message` URL for a given channel + message.
|
||||||
*
|
*
|
||||||
* Empty `threadRootId` is treated as "no thread" so callers can pass through
|
* Empty `threadRootId` is treated as "no thread" so callers can pass through
|
||||||
* the result of `getThreadReference(tags).rootId` without extra null checks.
|
* the result of `getThreadReference(tags).rootId` without extra null checks.
|
||||||
@@ -54,12 +53,14 @@ export function buildMessageLink(input: MessageLinkInput): string {
|
|||||||
if (input.threadRootId) {
|
if (input.threadRootId) {
|
||||||
params.set("thread", input.threadRootId);
|
params.set("thread", input.threadRootId);
|
||||||
}
|
}
|
||||||
return `sprout://${MESSAGE_LINK_HOST}?${params.toString()}`;
|
return `${MESSAGE_LINK_SCHEME}//${MESSAGE_LINK_HOST}?${params.toString()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a `sprout://message?…` URL. Returns a discriminated result so
|
* Parse a `buzz://message?…` URL. Legacy `sprout://message?…` links are
|
||||||
* callers can render a fallback (e.g. a plain link) without throwing.
|
* accepted so already-copied message links keep working during the rename.
|
||||||
|
* Returns a discriminated result so callers can render a fallback (e.g. a
|
||||||
|
* plain link) without throwing.
|
||||||
*/
|
*/
|
||||||
export function parseMessageLink(url: string): MessageLinkParseResult {
|
export function parseMessageLink(url: string): MessageLinkParseResult {
|
||||||
let parsed: URL;
|
let parsed: URL;
|
||||||
@@ -69,10 +70,13 @@ export function parseMessageLink(url: string): MessageLinkParseResult {
|
|||||||
return { ok: false, reason: "invalid-url" };
|
return { ok: false, reason: "invalid-url" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsed.protocol !== "sprout:") {
|
if (
|
||||||
|
parsed.protocol !== MESSAGE_LINK_SCHEME &&
|
||||||
|
parsed.protocol !== LEGACY_MESSAGE_LINK_SCHEME
|
||||||
|
) {
|
||||||
return { ok: false, reason: "wrong-scheme" };
|
return { ok: false, reason: "wrong-scheme" };
|
||||||
}
|
}
|
||||||
// `new URL("sprout://message?…")` puts "message" in `hostname`.
|
// `new URL("buzz://message?…")` puts "message" in `hostname`.
|
||||||
if (parsed.hostname !== MESSAGE_LINK_HOST) {
|
if (parsed.hostname !== MESSAGE_LINK_HOST) {
|
||||||
return { ok: false, reason: "wrong-host" };
|
return { ok: false, reason: "wrong-host" };
|
||||||
}
|
}
|
||||||
@@ -97,10 +101,15 @@ export function parseMessageLink(url: string): MessageLinkParseResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience: returns true if the given href is a `sprout://message` link.
|
* Convenience: returns true if the given href is a supported message link.
|
||||||
* Cheap pre-check used by the markdown renderer before parsing.
|
* Cheap pre-check used by the markdown renderer before parsing.
|
||||||
*/
|
*/
|
||||||
export function isMessageLink(href: string | undefined | null): boolean {
|
export function isMessageLink(href: string | undefined | null): boolean {
|
||||||
if (!href) return false;
|
if (!href) return false;
|
||||||
return href.startsWith("sprout://message?") || href === "sprout://message";
|
return (
|
||||||
|
href.startsWith("buzz://message?") ||
|
||||||
|
href === "buzz://message" ||
|
||||||
|
href.startsWith("sprout://message?") ||
|
||||||
|
href === "sprout://message"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Remark plugin that detects bare `sprout://message?…` URLs in text nodes and
|
* Remark plugin that detects bare `buzz://message?…` URLs in text nodes and
|
||||||
* replaces each with a custom `message-link` HAST element. The `markdown.tsx`
|
* replaces each with a custom `message-link` HAST element. Legacy
|
||||||
|
* `sprout://message?…` URLs are accepted during the rename. The `markdown.tsx`
|
||||||
* components map renders that as an inline pill (channel name + click-to-open)
|
* components map renders that as an inline pill (channel name + click-to-open)
|
||||||
* instead of the raw 100-char URL.
|
* instead of the raw 100-char URL.
|
||||||
*
|
*
|
||||||
* Why this plugin exists: `remark-gfm`'s autolinker only covers `http(s)://`
|
* Why this plugin exists: `remark-gfm`'s autolinker only covers `http(s)://`
|
||||||
* and `www.`. Custom schemes like `sprout://` only reach the `<a>` component
|
* and `www.`. Custom schemes like `buzz://` only reach the `<a>` component
|
||||||
* override when the user wrote an explicit `[label](sprout://…)` link.
|
* override when the user wrote an explicit `[label](buzz://…)` link.
|
||||||
*
|
*
|
||||||
* Mirrors `remarkChannelLinks` / `remarkMentions` — same factory, same HAST
|
* Mirrors `remarkChannelLinks` / `remarkMentions` — same factory, same HAST
|
||||||
* shape — so the rendering layer treats all three uniformly. Trailing
|
* shape — so the rendering layer treats all three uniformly. Trailing
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
// --experimental-strip-types`. `tsconfig.json` enables `allowImportingTsExtensions`.
|
// --experimental-strip-types`. `tsconfig.json` enables `allowImportingTsExtensions`.
|
||||||
import { createRemarkPrefixPlugin } from "../../../shared/lib/createRemarkPrefixPlugin.ts";
|
import { createRemarkPrefixPlugin } from "../../../shared/lib/createRemarkPrefixPlugin.ts";
|
||||||
|
|
||||||
const MESSAGE_URL_PATTERN = /sprout:\/\/message\?[^\s<>"')\]]+/g;
|
const MESSAGE_URL_PATTERN = /(?:buzz|sprout):\/\/message\?[^\s<>"')\]]+/g;
|
||||||
const TRAILING_PUNCTUATION_PATTERN = /[.,;:!?]+$/;
|
const TRAILING_PUNCTUATION_PATTERN = /[.,;:!?]+$/;
|
||||||
|
|
||||||
function trimMessageLinkMatch(matchText: string) {
|
function trimMessageLinkMatch(matchText: string) {
|
||||||
|
|||||||
@@ -304,10 +304,11 @@ export function useRichTextEditor({
|
|||||||
openOnClick: false,
|
openOnClick: false,
|
||||||
autolink: true,
|
autolink: true,
|
||||||
linkOnPaste: true,
|
linkOnPaste: true,
|
||||||
// Allow `sprout://` (used by Copy-link-to-message + sprout://connect)
|
// Allow Buzz message links through TipTap's URL sanitiser. Keep the
|
||||||
// through TipTap's URL sanitiser. http(s) and mailto are accepted by
|
// legacy Sprout protocol so already-copied links survive paste.
|
||||||
// default; non-listed protocols are stripped on paste/typed input.
|
// http(s) and mailto are accepted by default; non-listed protocols are
|
||||||
protocols: ["sprout"],
|
// stripped on paste/typed input.
|
||||||
|
protocols: ["buzz", "sprout"],
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: "text-primary underline underline-offset-4 cursor-pointer",
|
class: "text-primary underline underline-offset-4 cursor-pointer",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export interface DeepLinkDeps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Payload emitted by the Rust deep-link handler for `sprout://message?…`.
|
* Payload emitted by the Rust deep-link handler for `buzz://message?…`.
|
||||||
* Field names match the JSON shape produced in `desktop/src-tauri/src/lib.rs`.
|
* Field names match the JSON shape produced in `desktop/src-tauri/src/lib.rs`.
|
||||||
*/
|
*/
|
||||||
export type MessageDeepLinkPayload = {
|
export type MessageDeepLinkPayload = {
|
||||||
@@ -26,11 +26,11 @@ export type MessageDeepLinkPayload = {
|
|||||||
/**
|
/**
|
||||||
* Register listeners for deep-link events emitted by the Rust backend.
|
* Register listeners for deep-link events emitted by the Rust backend.
|
||||||
*
|
*
|
||||||
* When a `sprout://connect?relay=<url>` link is opened, the handler
|
* When a `buzz://connect?relay=<url>` link is opened, the handler
|
||||||
* adds a workspace for the relay (deduplicating by URL) and switches
|
* adds a workspace for the relay (deduplicating by URL) and switches
|
||||||
* to it. Returns an unlisten function to tear down all listeners.
|
* to it. Returns an unlisten function to tear down all listeners.
|
||||||
*
|
*
|
||||||
* `sprout://message?…` is handled separately by `listenForMessageDeepLinks`,
|
* `buzz://message?…` is handled separately by `listenForMessageDeepLinks`,
|
||||||
* because it needs to dispatch into the router which only exists below the
|
* because it needs to dispatch into the router which only exists below the
|
||||||
* `RouterProvider` in the component tree.
|
* `RouterProvider` in the component tree.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -428,12 +428,12 @@ test("rehypeImageGallery: mixed content paragraph is not image-only", () => {
|
|||||||
assert.equal(tree.children.length, 3);
|
assert.equal(tree.children.length, 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── messageLinkUrlTransform: sprout:// link preservation ──────────────
|
// ── messageLinkUrlTransform: buzz:// link preservation ──────────────
|
||||||
// Regression test: react-markdown's `defaultUrlTransform` strips unknown
|
// Regression test: react-markdown's `defaultUrlTransform` strips unknown
|
||||||
// schemes (returns `""`) before our `a` component override can see them,
|
// schemes (returns `""`) before our `a` component override can see them,
|
||||||
// which would break copy → paste → click for `sprout://message?…` links
|
// which would break copy → paste → click for `buzz://message?…` links
|
||||||
// end-to-end. We pass a custom `urlTransform` that delegates to the
|
// end-to-end. We pass a custom `urlTransform` that delegates to the
|
||||||
// default for everything except `sprout://message` hrefs.
|
// default for `buzz://message` and legacy `sprout://message` hrefs.
|
||||||
//
|
//
|
||||||
// This test renders real `<ReactMarkdown>` with the production transform
|
// This test renders real `<ReactMarkdown>` with the production transform
|
||||||
// and asserts the link href survives to the rendered DOM. Mirrors the
|
// and asserts the link href survives to the rendered DOM. Mirrors the
|
||||||
@@ -462,19 +462,19 @@ function renderMarkdown(content) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
test("messageLinkUrlTransform: preserves sprout://message href", () => {
|
test("messageLinkUrlTransform: preserves buzz://message href", () => {
|
||||||
const html = renderMarkdown(
|
const html = renderMarkdown(
|
||||||
"Click [here](sprout://message?channel=abc&id=xyz)",
|
"Click [here](buzz://message?channel=abc&id=xyz)",
|
||||||
);
|
);
|
||||||
// HTML-encoded `&` in attributes is fine — the browser decodes back to `&`.
|
// HTML-encoded `&` in attributes is fine — the browser decodes back to `&`.
|
||||||
assert.match(html, /href="sprout:\/\/message\?channel=abc&(?:amp;)?id=xyz"/);
|
assert.match(html, /href="buzz:\/\/message\?channel=abc&(?:amp;)?id=xyz"/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("messageLinkUrlTransform: preserves sprout://message href with thread", () => {
|
test("messageLinkUrlTransform: preserves buzz://message href with thread", () => {
|
||||||
const html = renderMarkdown(
|
const html = renderMarkdown(
|
||||||
"[link](sprout://message?channel=c1&id=m1&thread=t1)",
|
"[link](buzz://message?channel=c1&id=m1&thread=t1)",
|
||||||
);
|
);
|
||||||
assert.match(html, /href="sprout:\/\/message\?[^"]*thread=t1"/);
|
assert.match(html, /href="buzz:\/\/message\?[^"]*thread=t1"/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("messageLinkUrlTransform: still strips javascript: scheme", () => {
|
test("messageLinkUrlTransform: still strips javascript: scheme", () => {
|
||||||
@@ -489,18 +489,25 @@ test("messageLinkUrlTransform: passes http(s) through unchanged", () => {
|
|||||||
assert.match(html, /href="https:\/\/example\.com\/path"/);
|
assert.match(html, /href="https:\/\/example\.com\/path"/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("messageLinkUrlTransform: leaves non-message sprout:// schemes to default", () => {
|
test("messageLinkUrlTransform: preserves legacy sprout://message href", () => {
|
||||||
// `sprout://connect?relay=…` is handled by a different code path (Tauri
|
const html = renderMarkdown(
|
||||||
|
"Click [here](sprout://message?channel=abc&id=xyz)",
|
||||||
|
);
|
||||||
|
assert.match(html, /href="sprout:\/\/message\?channel=abc&(?:amp;)?id=xyz"/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("messageLinkUrlTransform: leaves non-message buzz:// schemes to default", () => {
|
||||||
|
// `buzz://connect?relay=…` is handled by a different code path (Tauri
|
||||||
// single-instance). The markdown renderer should let it pass through
|
// single-instance). The markdown renderer should let it pass through
|
||||||
// defaultUrlTransform (which strips it) since it's not clickable in-app.
|
// defaultUrlTransform (which strips it) since it's not clickable in-app.
|
||||||
const html = renderMarkdown(
|
const html = renderMarkdown(
|
||||||
"[connect](sprout://connect?relay=wss://relay.example)",
|
"[connect](buzz://connect?relay=wss://relay.example)",
|
||||||
);
|
);
|
||||||
assert.match(html, /href=""/);
|
assert.match(html, /href=""/);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── remarkMessageLinks: bare-URL → message-link node ──────────────────
|
// ── remarkMessageLinks: bare-URL → message-link node ──────────────────
|
||||||
// `remark-gfm`'s autolinker only covers http(s)://, so bare `sprout://message`
|
// `remark-gfm`'s autolinker only covers http(s)://, so bare `buzz://message`
|
||||||
// URLs in plain text never reach any rendering path without this plugin.
|
// URLs in plain text never reach any rendering path without this plugin.
|
||||||
// The plugin emits a custom `message-link` HAST element which markdown.tsx
|
// The plugin emits a custom `message-link` HAST element which markdown.tsx
|
||||||
// renders as an inline pill. Tests operate on the mdast tree directly —
|
// renders as an inline pill. Tests operate on the mdast tree directly —
|
||||||
@@ -521,18 +528,26 @@ function text(value) {
|
|||||||
return { type: "text", value };
|
return { type: "text", value };
|
||||||
}
|
}
|
||||||
|
|
||||||
test("remarkMessageLinks: bare sprout://message URL is replaced", () => {
|
test("remarkMessageLinks: bare buzz://message URL is replaced", () => {
|
||||||
|
const tree = runPlugin(paragraph(text("buzz://message?channel=c&id=m")));
|
||||||
|
const para = tree.children[0];
|
||||||
|
assert.equal(para.children.length, 1);
|
||||||
|
assert.equal(para.children[0].type, "message-link");
|
||||||
|
assert.equal(para.children[0].value, "buzz://message?channel=c&id=m");
|
||||||
|
assert.equal(para.children[0].data.hName, "message-link");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("remarkMessageLinks: legacy bare sprout://message URL is replaced", () => {
|
||||||
const tree = runPlugin(paragraph(text("sprout://message?channel=c&id=m")));
|
const tree = runPlugin(paragraph(text("sprout://message?channel=c&id=m")));
|
||||||
const para = tree.children[0];
|
const para = tree.children[0];
|
||||||
assert.equal(para.children.length, 1);
|
assert.equal(para.children.length, 1);
|
||||||
assert.equal(para.children[0].type, "message-link");
|
assert.equal(para.children[0].type, "message-link");
|
||||||
assert.equal(para.children[0].value, "sprout://message?channel=c&id=m");
|
assert.equal(para.children[0].value, "sprout://message?channel=c&id=m");
|
||||||
assert.equal(para.children[0].data.hName, "message-link");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("remarkMessageLinks: mid-sentence URL splits surrounding text", () => {
|
test("remarkMessageLinks: mid-sentence URL splits surrounding text", () => {
|
||||||
const tree = runPlugin(
|
const tree = runPlugin(
|
||||||
paragraph(text("see sprout://message?channel=c&id=m here")),
|
paragraph(text("see buzz://message?channel=c&id=m here")),
|
||||||
);
|
);
|
||||||
const kids = tree.children[0].children;
|
const kids = tree.children[0].children;
|
||||||
assert.equal(kids.length, 3);
|
assert.equal(kids.length, 3);
|
||||||
@@ -547,28 +562,28 @@ test("remarkMessageLinks: two URLs in one text node both replaced", () => {
|
|||||||
const tree = runPlugin(
|
const tree = runPlugin(
|
||||||
paragraph(
|
paragraph(
|
||||||
text(
|
text(
|
||||||
"first sprout://message?channel=a&id=1 then sprout://message?channel=b&id=2 done",
|
"first buzz://message?channel=a&id=1 then buzz://message?channel=b&id=2 done",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const kids = tree.children[0].children;
|
const kids = tree.children[0].children;
|
||||||
const links = kids.filter((c) => c.type === "message-link");
|
const links = kids.filter((c) => c.type === "message-link");
|
||||||
assert.equal(links.length, 2);
|
assert.equal(links.length, 2);
|
||||||
assert.equal(links[0].value, "sprout://message?channel=a&id=1");
|
assert.equal(links[0].value, "buzz://message?channel=a&id=1");
|
||||||
assert.equal(links[1].value, "sprout://message?channel=b&id=2");
|
assert.equal(links[1].value, "buzz://message?channel=b&id=2");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("remarkMessageLinks: trailing sentence punctuation stays outside URL", () => {
|
test("remarkMessageLinks: trailing sentence punctuation stays outside URL", () => {
|
||||||
for (const punctuation of [".", ",", ";", ":", "!", "?"]) {
|
for (const punctuation of [".", ",", ";", ":", "!", "?"]) {
|
||||||
const tree = runPlugin(
|
const tree = runPlugin(
|
||||||
paragraph(text(`see sprout://message?channel=c&id=m${punctuation}`)),
|
paragraph(text(`see buzz://message?channel=c&id=m${punctuation}`)),
|
||||||
);
|
);
|
||||||
const kids = tree.children[0].children;
|
const kids = tree.children[0].children;
|
||||||
|
|
||||||
assert.equal(kids.length, 3, punctuation);
|
assert.equal(kids.length, 3, punctuation);
|
||||||
assert.equal(kids[0].value, "see ", punctuation);
|
assert.equal(kids[0].value, "see ", punctuation);
|
||||||
assert.equal(kids[1].type, "message-link", punctuation);
|
assert.equal(kids[1].type, "message-link", punctuation);
|
||||||
assert.equal(kids[1].value, "sprout://message?channel=c&id=m", punctuation);
|
assert.equal(kids[1].value, "buzz://message?channel=c&id=m", punctuation);
|
||||||
assert.equal(kids[2].type, "text", punctuation);
|
assert.equal(kids[2].type, "text", punctuation);
|
||||||
assert.equal(kids[2].value, punctuation, punctuation);
|
assert.equal(kids[2].value, punctuation, punctuation);
|
||||||
}
|
}
|
||||||
@@ -576,20 +591,20 @@ test("remarkMessageLinks: trailing sentence punctuation stays outside URL", () =
|
|||||||
|
|
||||||
test("remarkMessageLinks: URL inside parens keeps closing paren outside", () => {
|
test("remarkMessageLinks: URL inside parens keeps closing paren outside", () => {
|
||||||
const tree = runPlugin(
|
const tree = runPlugin(
|
||||||
paragraph(text("see (sprout://message?channel=c&id=m) for details")),
|
paragraph(text("see (buzz://message?channel=c&id=m) for details")),
|
||||||
);
|
);
|
||||||
const kids = tree.children[0].children;
|
const kids = tree.children[0].children;
|
||||||
|
|
||||||
assert.equal(kids.length, 3);
|
assert.equal(kids.length, 3);
|
||||||
assert.equal(kids[0].value, "see (");
|
assert.equal(kids[0].value, "see (");
|
||||||
assert.equal(kids[1].type, "message-link");
|
assert.equal(kids[1].type, "message-link");
|
||||||
assert.equal(kids[1].value, "sprout://message?channel=c&id=m");
|
assert.equal(kids[1].value, "buzz://message?channel=c&id=m");
|
||||||
assert.equal(kids[2].type, "text");
|
assert.equal(kids[2].type, "text");
|
||||||
assert.equal(kids[2].value, ") for details");
|
assert.equal(kids[2].value, ") for details");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("remarkMessageLinks: URL without trailing punctuation matches end-to-end", () => {
|
test("remarkMessageLinks: URL without trailing punctuation matches end-to-end", () => {
|
||||||
const value = "sprout://message?channel=c&id=m";
|
const value = "buzz://message?channel=c&id=m";
|
||||||
const tree = runPlugin(paragraph(text(value)));
|
const tree = runPlugin(paragraph(text(value)));
|
||||||
const kids = tree.children[0].children;
|
const kids = tree.children[0].children;
|
||||||
|
|
||||||
@@ -598,8 +613,8 @@ test("remarkMessageLinks: URL without trailing punctuation matches end-to-end",
|
|||||||
assert.equal(kids[0].value, value);
|
assert.equal(kids[0].value, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("remarkMessageLinks: non-message sprout:// URLs are not matched", () => {
|
test("remarkMessageLinks: non-message buzz:// URLs are not matched", () => {
|
||||||
const original = "sprout://connect?relay=wss://x.example";
|
const original = "buzz://connect?relay=wss://x.example";
|
||||||
const tree = runPlugin(paragraph(text(original)));
|
const tree = runPlugin(paragraph(text(original)));
|
||||||
const kids = tree.children[0].children;
|
const kids = tree.children[0].children;
|
||||||
assert.equal(kids.length, 1);
|
assert.equal(kids.length, 1);
|
||||||
@@ -618,7 +633,7 @@ test("remarkMessageLinks: text inside inlineCode is left alone", () => {
|
|||||||
{
|
{
|
||||||
type: "paragraph",
|
type: "paragraph",
|
||||||
children: [
|
children: [
|
||||||
{ type: "inlineCode", value: "sprout://message?channel=c&id=m" },
|
{ type: "inlineCode", value: "buzz://message?channel=c&id=m" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -627,5 +642,5 @@ test("remarkMessageLinks: text inside inlineCode is left alone", () => {
|
|||||||
const kids = tree.children[0].children;
|
const kids = tree.children[0].children;
|
||||||
assert.equal(kids.length, 1);
|
assert.equal(kids.length, 1);
|
||||||
assert.equal(kids[0].type, "inlineCode");
|
assert.equal(kids[0].type, "inlineCode");
|
||||||
assert.equal(kids[0].value, "sprout://message?channel=c&id=m");
|
assert.equal(kids[0].value, "buzz://message?channel=c&id=m");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ function useStableArray<T>(arr: T[]): T[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `urlTransform` for `<ReactMarkdown>` that preserves `sprout://message?…`
|
* `urlTransform` for `<ReactMarkdown>` that preserves `buzz://message?…`
|
||||||
* links. The default transform strips unknown schemes (returns `""`) before
|
* links. The default transform strips unknown schemes (returns `""`) before
|
||||||
* the `a` component override can see them, which would break copy → paste →
|
* the `a` component override can see them, which would break copy → paste →
|
||||||
* click end-to-end. Everything else delegates to `defaultUrlTransform`.
|
* click end-to-end. Everything else delegates to `defaultUrlTransform`.
|
||||||
@@ -709,7 +709,7 @@ function createMarkdownComponents(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intercept `sprout://message?channel=…&id=…` links so a click navigates
|
// Intercept `buzz://message?channel=…&id=…` links so a click navigates
|
||||||
// in-app instead of opening the URL in the OS browser. http(s) links
|
// in-app instead of opening the URL in the OS browser. http(s) links
|
||||||
// continue to use the existing target="_blank" behavior.
|
// continue to use the existing target="_blank" behavior.
|
||||||
if (isMessageLink(href)) {
|
if (isMessageLink(href)) {
|
||||||
@@ -730,7 +730,7 @@ function createMarkdownComponents(
|
|||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Malformed sprout://message link — fall through to the default
|
// Malformed message deep link — fall through to the default
|
||||||
// anchor (renders as a normal external link).
|
// anchor (renders as a normal external link).
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -991,7 +991,7 @@ function createMarkdownComponents(
|
|||||||
const href = String(children ?? "");
|
const href = String(children ?? "");
|
||||||
const parsed = parseMessageLink(href);
|
const parsed = parseMessageLink(href);
|
||||||
if (!parsed.ok) {
|
if (!parsed.ok) {
|
||||||
// Malformed `sprout://message?…` — render the raw URL as plain text
|
// Malformed `buzz://message?…` — render the raw URL as plain text
|
||||||
// rather than a misleading clickable pill.
|
// rather than a misleading clickable pill.
|
||||||
return <span data-message-link="">{href}</span>;
|
return <span data-message-link="">{href}</span>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useAppNavigation } from "@/app/navigation/useAppNavigation";
|
|||||||
import { listenForMessageDeepLinks } from "@/shared/deep-link";
|
import { listenForMessageDeepLinks } from "@/shared/deep-link";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to `sprout://message` deep links emitted by the Tauri backend
|
* Subscribe to `buzz://message` deep links emitted by the Tauri backend
|
||||||
* and route them through the app's navigation helpers.
|
* and route them through the app's navigation helpers.
|
||||||
*
|
*
|
||||||
* Lives in a hook (not inline in `AppShell`) so it can be unit-tested
|
* Lives in a hook (not inline in `AppShell`) so it can be unit-tested
|
||||||
@@ -12,7 +12,7 @@ import { listenForMessageDeepLinks } from "@/shared/deep-link";
|
|||||||
*
|
*
|
||||||
* Mirrors the cold-start race handling of the `connect` listener in
|
* Mirrors the cold-start race handling of the `connect` listener in
|
||||||
* `App.tsx`: late-arriving payloads from a fresh launch are picked up the
|
* `App.tsx`: late-arriving payloads from a fresh launch are picked up the
|
||||||
* first time the listener mounts. Routing matches the in-app sprout://
|
* first time the listener mounts. Routing matches the in-app buzz://
|
||||||
* handler in `markdown.tsx`: always `goChannel` with `messageId` and let
|
* handler in `markdown.tsx`: always `goChannel` with `messageId` and let
|
||||||
* the channel route's existing scroll-into-view + getEventById backfill
|
* the channel route's existing scroll-into-view + getEventById backfill
|
||||||
* resolve the target (works for both stream replies and forum threads).
|
* resolve the target (works for both stream replies and forum threads).
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "sprout-workspace",
|
"name": "buzz-workspace",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@11.4.0",
|
"packageManager": "pnpm@11.4.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Sprout</title>
|
<title>Buzz</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "sprout-web",
|
"name": "buzz-web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function RootLayout() {
|
|||||||
to="/"
|
to="/"
|
||||||
className="text-sm font-semibold tracking-tight text-foreground"
|
className="text-sm font-semibold tracking-tight text-foreground"
|
||||||
>
|
>
|
||||||
Sprout
|
Buzz
|
||||||
</Link>
|
</Link>
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { relayHttpBaseUrl } from "@/shared/lib/relay-url";
|
|||||||
|
|
||||||
/** Get a repo-specific LightningFS instance backed by IndexedDB. */
|
/** Get a repo-specific LightningFS instance backed by IndexedDB. */
|
||||||
export function getFs(owner: string, repoName: string): LightningFS {
|
export function getFs(owner: string, repoName: string): LightningFS {
|
||||||
return new LightningFS(`sprout-git-${owner}-${repoName}`);
|
return new LightningFS(`buzz-git-${owner}-${repoName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Working directory inside the virtual FS. */
|
/** Working directory inside the virtual FS. */
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import { relayWsUrl } from "@/shared/lib/relay-url";
|
|||||||
import { Button } from "@/shared/ui/button";
|
import { Button } from "@/shared/ui/button";
|
||||||
|
|
||||||
export function ConnectButton({ className }: { className?: string }) {
|
export function ConnectButton({ className }: { className?: string }) {
|
||||||
const deepLink = `sprout://connect?relay=${encodeURIComponent(relayWsUrl())}`;
|
const deepLink = `buzz://connect?relay=${encodeURIComponent(relayWsUrl())}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button asChild className={className}>
|
<Button asChild className={className}>
|
||||||
<a href={deepLink}>
|
<a href={deepLink}>
|
||||||
<ExternalLink className="h-4 w-4" />
|
<ExternalLink className="h-4 w-4" />
|
||||||
Open in Sprout
|
Open in Buzz
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function OrgSidebar({ repos }: { repos: Repo[] }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Open in Sprout */}
|
{/* Open in Buzz */}
|
||||||
<ConnectButton className="w-full" />
|
<ConnectButton className="w-full" />
|
||||||
|
|
||||||
{/* People section */}
|
{/* People section */}
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ export function RepoDetailPage() {
|
|||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<aside className="hidden w-72 shrink-0 border-l border-border pl-8 lg:block">
|
<aside className="hidden w-72 shrink-0 border-l border-border pl-8 lg:block">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Open in Sprout */}
|
{/* Open in Buzz */}
|
||||||
<ConnectButton className="w-full" />
|
<ConnectButton className="w-full" />
|
||||||
|
|
||||||
{/* People */}
|
{/* People */}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BookMarked, GitBranch, Sprout } from "lucide-react";
|
import { BookMarked, GitBranch, Hexagon } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
@@ -45,14 +45,14 @@ function RelayEmptyState() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col items-center justify-center px-4 text-center">
|
<div className="flex flex-1 flex-col items-center justify-center px-4 text-center">
|
||||||
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-primary/10">
|
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-primary/10">
|
||||||
<Sprout className="h-8 w-8 text-primary" />
|
<Hexagon className="h-8 w-8 text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<h1 className="mt-6 text-2xl font-semibold tracking-tight">
|
<h1 className="mt-6 text-2xl font-semibold tracking-tight">
|
||||||
This relay is empty
|
This relay is empty
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-2 max-w-md text-sm leading-relaxed text-muted-foreground">
|
<p className="mt-2 max-w-md text-sm leading-relaxed text-muted-foreground">
|
||||||
Repositories pushed to this relay will show up here. Open this relay in
|
Repositories pushed to this relay will show up here. Open this relay in
|
||||||
the Sprout desktop app to start pushing code.
|
the Buzz desktop app to start pushing code.
|
||||||
</p>
|
</p>
|
||||||
<ConnectButton className="mt-6" />
|
<ConnectButton className="mt-6" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export function queryEvents(
|
|||||||
|
|
||||||
ws.addEventListener("open", () => {
|
ws.addEventListener("open", () => {
|
||||||
// Wait briefly for an AUTH challenge before sending REQ.
|
// Wait briefly for an AUTH challenge before sending REQ.
|
||||||
// Sprout relays always send AUTH, but other relays may not.
|
// Buzz relays always send AUTH, but other relays may not.
|
||||||
setTimeout(() => sendReq(), 100);
|
setTimeout(() => sendReq(), 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type ThemeContextValue = {
|
|||||||
setTheme: (theme: Theme) => void;
|
setTheme: (theme: Theme) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const STORAGE_KEY = "sprout-web-theme";
|
const STORAGE_KEY = "buzz-web-theme";
|
||||||
|
|
||||||
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
test("home page loads with Sprout heading", async ({ page }) => {
|
test("home page loads with Buzz heading", async ({ page }) => {
|
||||||
await page.goto("/");
|
await page.goto("/");
|
||||||
await expect(page.locator("header")).toContainText("Sprout");
|
await expect(page.locator("header")).toContainText("Buzz");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("home page shows repositories section", async ({ page }) => {
|
test("home page shows repositories section", async ({ page }) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user