From 454b16ccd428a2641c26fcd869a8b6a98ee4fbab Mon Sep 17 00:00:00 2001 From: zx <67887489+tan-zx@users.noreply.github.com> Date: Fri, 17 Jan 2025 22:20:44 +0800 Subject: [PATCH 01/15] lower concurrent inits --- packages/wrangler/src/workflows/sync/sync.param.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wrangler/src/workflows/sync/sync.param.ts b/packages/wrangler/src/workflows/sync/sync.param.ts index 78a4ce34..c2ecab94 100644 --- a/packages/wrangler/src/workflows/sync/sync.param.ts +++ b/packages/wrangler/src/workflows/sync/sync.param.ts @@ -2,7 +2,7 @@ // where we truncate body size and code blocks //* Parameters for init workflow *// -export const NUM_CONCURRENT_INITS = 10; +export const NUM_CONCURRENT_INITS = 5; // for GitHub API calls export const NUM_EMBEDDING_WORKERS = 3; // also corresponds to number of consecutive API calls + upserts before spinning up workers From 5289e2cc3bfc27b4c5a0e304fd73f959e8d9b50d Mon Sep 17 00:00:00 2001 From: zx <67887489+tan-zx@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:32:34 +0800 Subject: [PATCH 02/15] logo tweak --- packages/web/src/components/navbar/Navbar.tsx | 11 ++++------- packages/web/tailwind.config.js | 1 + 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/web/src/components/navbar/Navbar.tsx b/packages/web/src/components/navbar/Navbar.tsx index 9ef9d179..3eb09d30 100644 --- a/packages/web/src/components/navbar/Navbar.tsx +++ b/packages/web/src/components/navbar/Navbar.tsx @@ -12,13 +12,10 @@ export function Navbar() {
-

- S - e - m - H - u - b +

+ Sem + Hub + _

Sem diff --git a/packages/web/tailwind.config.js b/packages/web/tailwind.config.js index e14c89e3..1f2adcad 100644 --- a/packages/web/tailwind.config.js +++ b/packages/web/tailwind.config.js @@ -88,6 +88,7 @@ export default { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", cursor: "cursor-blink 1s step-end infinite", + "cursor-slow": "cursor-blink 2s step-end infinite", }, fontSize: { "mobile-base": "0.875rem", // 14px From f9fcf111ba33fa753a33e3af725971dd3337fa03 Mon Sep 17 00:00:00 2001 From: zx <67887489+tan-zx@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:49:20 +0800 Subject: [PATCH 03/15] backend --- packages/core/src/repo.ts | 39 +++++++++++++++++++ packages/web/src/lib/api/repo.ts | 8 ++++ .../src/server/router/public/public.router.ts | 5 ++- .../src/server/router/public/repo.router.ts | 28 +++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 packages/workers/src/server/router/public/repo.router.ts diff --git a/packages/core/src/repo.ts b/packages/core/src/repo.ts index 1b830324..113699df 100644 --- a/packages/core/src/repo.ts +++ b/packages/core/src/repo.ts @@ -392,4 +392,43 @@ export const Repo = { .set({ issuesLastUpdatedAt: result.lastUpdated }) .where(eq(repos.id, repoId)); }, + + readyForPublicSearch: async ({ + owner, + name, + db, + }: { + owner: string; + name: string; + db: DbClient; + }) => { + const [result] = await db + .select({ + initStatus: repos.initStatus, + syncStatus: repos.syncStatus, + lastSyncedAt: repos.lastSyncedAt, + issuesLastUpdatedAt: repos.issuesLastUpdatedAt, + avatarUrl: repos.ownerAvatarUrl, + }) + .from(repos) + .where( + and( + eq(repos.ownerLogin, owner), + eq(repos.name, name), + eq(repos.isPrivate, false), + ), + ); + + if (!result) { + return null; + } + + return { + initStatus: result.initStatus, + lastSyncedAt: result.lastSyncedAt, + issuesLastUpdatedAt: result.issuesLastUpdatedAt, + syncStatus: result.syncStatus, + avatarUrl: result.avatarUrl, + }; + }, }; diff --git a/packages/web/src/lib/api/repo.ts b/packages/web/src/lib/api/repo.ts index 72589590..15be07a2 100644 --- a/packages/web/src/lib/api/repo.ts +++ b/packages/web/src/lib/api/repo.ts @@ -26,3 +26,11 @@ export const unsubscribeRepo = async (repoId: string) => { }); return handleResponse(response, "Failed to unsubscribe from repository"); }; + +export async function getRepoStatus(owner: string, repo: string) { + const response = await fetch(`/api/public/repo/${owner}/${repo}/status`); + if (!response.ok) { + throw new Error("Failed to get repository status"); + } + return response.json(); +} diff --git a/packages/workers/src/server/router/public/public.router.ts b/packages/workers/src/server/router/public/public.router.ts index abb8bbce..9a0beda3 100644 --- a/packages/workers/src/server/router/public/public.router.ts +++ b/packages/workers/src/server/router/public/public.router.ts @@ -2,6 +2,9 @@ import { Hono } from "hono"; import type { Context } from "@/server/app"; +import { repoRouter } from "./repo.router"; import { searchRouter } from "./search.router"; -export const publicRouter = new Hono().route("/search", searchRouter); +export const publicRouter = new Hono() + .route("/search", searchRouter) + .route("/repo", repoRouter); diff --git a/packages/workers/src/server/router/public/repo.router.ts b/packages/workers/src/server/router/public/repo.router.ts new file mode 100644 index 00000000..d585083e --- /dev/null +++ b/packages/workers/src/server/router/public/repo.router.ts @@ -0,0 +1,28 @@ +import { zValidator } from "@hono/zod-validator"; +import { Hono } from "hono"; + +import { repoValidationSchema } from "@/core/github/schema.validation"; +import { Repo } from "@/core/repo"; +import { getDeps } from "@/deps"; +import type { Context } from "@/server/app"; +import { createSuccessResponse } from "@/server/response"; + +export const repoRouter = new Hono().get( + "/:owner/:repo/status", + zValidator("param", repoValidationSchema), + async (c) => { + const { owner, repo } = c.req.valid("param"); + const { db } = getDeps(); + const res = await Repo.readyForPublicSearch({ + owner, + name: repo, + db, + }); + return c.json( + createSuccessResponse({ + data: res, + message: "Successfully retrieved repository status", + }), + ); + }, +); From 3f8dc11cfaacb2fdfc7cff8663069241b47c7b73 Mon Sep 17 00:00:00 2001 From: zx <67887489+tan-zx@users.noreply.github.com> Date: Fri, 17 Jan 2025 22:18:15 +0800 Subject: [PATCH 04/15] good stuff wip --- bun.lockb | Bin 393578 -> 393578 bytes .../src/components/search/RepoSearchBar.tsx | 108 +++++++++++++++++ packages/web/src/components/ui/breadcrumb.tsx | 114 ++++++++++++++++++ packages/web/src/lib/api/repo.ts | 13 +- packages/web/src/lib/hooks/useRepo.ts | 14 ++- packages/web/src/lib/queryClient.ts | 2 + packages/web/src/routeTree.gen.ts | 36 +++++- packages/web/src/routes/r/$owner/$repo.tsx | 47 ++++++++ 8 files changed, 324 insertions(+), 10 deletions(-) create mode 100644 packages/web/src/components/search/RepoSearchBar.tsx create mode 100644 packages/web/src/components/ui/breadcrumb.tsx create mode 100644 packages/web/src/routes/r/$owner/$repo.tsx diff --git a/bun.lockb b/bun.lockb index bd2507f248d54c315225c7e8fee823a0b654d55b..f192fdfd6ed30dfb612a87783461dab642c7f6b6 100755 GIT binary patch delta 35 ncmaFWB=M?AqM?Pcg{g&k3(M9>b|wZeXx|gbvVBh^>$>Ft+VTsq delta 35 pcmaFWB=M?AqM?Pcg{g&k3(M9>cE&hEJs@n~6UnlDPbBNQ handleSearch(e, query)}> +
+
+ + {query && ( + + )} + + +
+ {shouldShowDropdown && ( +
+ +
+ )} +
+ + +
+
+ + ); +} diff --git a/packages/web/src/components/ui/breadcrumb.tsx b/packages/web/src/components/ui/breadcrumb.tsx new file mode 100644 index 00000000..1fd4b2c0 --- /dev/null +++ b/packages/web/src/components/ui/breadcrumb.tsx @@ -0,0 +1,114 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cn } from "@/lib/utils" +import { ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons" + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) =>

{shouldShowDropdown && (
diff --git a/packages/web/src/components/ui/popover.tsx b/packages/web/src/components/ui/popover.tsx new file mode 100644 index 00000000..d82e7149 --- /dev/null +++ b/packages/web/src/components/ui/popover.tsx @@ -0,0 +1,31 @@ +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverAnchor = PopoverPrimitive.Anchor + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/packages/web/src/routes/r/$owner/$repo.tsx b/packages/web/src/routes/r/$owner/$repo.tsx index 6b105892..0215f6a3 100644 --- a/packages/web/src/routes/r/$owner/$repo.tsx +++ b/packages/web/src/routes/r/$owner/$repo.tsx @@ -1,14 +1,48 @@ import { createFileRoute } from "@tanstack/react-router"; +import { CheckIcon, CopyIcon, InfoIcon, PlusIcon } from "lucide-react"; +import { useState } from "react"; import { useRepoStatus } from "@/lib/hooks/useRepo"; +import { getTimeAgo } from "@/lib/time"; +import { Button } from "@/components/ui/button"; +import { FastTooltip } from "@/components/ui/fast-tooltip"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { TooltipProvider } from "@/components/ui/tooltip"; import { RepoSearchBar } from "@/components/search/RepoSearchBar"; export const Route = createFileRoute("/r/$owner/$repo")({ component: RepoSearch, }); +function TimeDisplay({ label, date }: { label: string; date: string | null }) { + if (!date) return null; + const dateObj = new Date(date); + return ( + + {label}: {getTimeAgo(dateObj)} + + ); +} + function RepoSearch() { const { owner, repo } = Route.useParams(); + const [copied, setCopied] = useState(false); + + const imgSrc = + "https://img.shields.io/badge/search-semhub-blue?style=flat&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI%2BCiAgPHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNNDE0IDM1NHEtMTgtMTgtNDEtMTFsLTMyLTMycTQzLTUzIDQzLTExOXEwLTgwLTU2LTEzNlQxOTIgMFQ1NiA1NlQwIDE5MnQ1NiAxMzZ0MTM2IDU2cTcwIDAgMTE5LTQzbDMyIDMycS02IDI0IDExIDQxbDg1IDg1cTEzIDEzIDMwIDEzcTE4IDAgMzAtMTNxMTMtMTMgMTMtMzB0LTEzLTMwem0tMjIyLTEzcS02MiAwLTEwNS41LTQzLjVUNDMgMTkyVDg2LjUgODYuNVQxOTIgNDN0MTA1LjUgNDMuNVQzNDEgMTkydC00My41IDEwNS41VDE5MiAzNDF6Ii8%2BCjwvc3ZnPg%3D%3D"; + + const embedCode = `Search with SemHub`; + + const copyToClipboard = () => { + navigator.clipboard.writeText(embedCode).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + }; const { data: repoStatus } = useRepoStatus(owner, repo); @@ -29,19 +63,109 @@ function RepoSearch() {
); } + const { avatarUrl, issuesLastUpdatedAt, lastSyncedAt } = repoStatus; return (
-

- - {owner}/{repo} - -

-
- +
+ +

+ Uncover insights with{" "} + Semantic + search for Git + Hub +

+
+ +
+
+ + + + +
+ } + > + + + + + + + + + +
+
+
Add to your repo
+

+ Add semantic search to your repository by embedding this badge + in your README and loading your repo into SemHub. +

+
+ Search with SemHub +
+
+
+
+                      {embedCode}
+                    
+ +
+
+
+
+
+
+
); } From e5b86d06b931ad2a2edcb6f00f3549c290abb81f Mon Sep 17 00:00:00 2001 From: zx <67887489+tan-zx@users.noreply.github.com> Date: Fri, 17 Jan 2025 23:31:25 +0800 Subject: [PATCH 07/15] refactor --- .../src/components/search/repo/components.tsx | 126 ++++++++++++++++++ packages/web/src/routes/r/$owner/$repo.tsx | 107 ++------------- 2 files changed, 134 insertions(+), 99 deletions(-) create mode 100644 packages/web/src/components/search/repo/components.tsx diff --git a/packages/web/src/components/search/repo/components.tsx b/packages/web/src/components/search/repo/components.tsx new file mode 100644 index 00000000..6cf8530c --- /dev/null +++ b/packages/web/src/components/search/repo/components.tsx @@ -0,0 +1,126 @@ +import { CheckIcon, CopyIcon, InfoIcon, PlusIcon } from "lucide-react"; +import { useState } from "react"; + +import { getTimeAgo } from "@/lib/time"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { FastTooltip } from "@/components/ui/fast-tooltip"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { TooltipProvider } from "@/components/ui/tooltip"; + +function TimeDisplay({ label, date }: { label: string; date: string | null }) { + if (!date) return null; + const dateObj = new Date(date); + return ( + + {label}: {getTimeAgo(dateObj)} + + ); +} + +export function RepoStatusTooltip({ + lastSyncedAt, + issuesLastUpdatedAt, +}: { + lastSyncedAt: string | null; + issuesLastUpdatedAt: string | null; +}) { + return ( + + + + +
+ } + > + + + + + + ); +} + +export function EmbedBadgePopover({ + owner, + repo, +}: { + owner: string; + repo: string; +}) { + const [copied, setCopied] = useState(false); + + const imgSrc = + "https://img.shields.io/badge/search-semhub-blue?style=flat&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI%2BCiAgPHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNNDE0IDM1NHEtMTgtMTgtNDEtMTFsLTMyLTMycTQzLTUzIDQzLTExOXEwLTgwLTU2LTEzNlQxOTIgMFQ1NiA1NlQwIDE5MnQ1NiAxMzZ0MTM2IDU2cTcwIDAgMTE5LTQzbDMyIDMycS02IDI0IDExIDQxbDg1IDg1cTEzIDEzIDMwIDEzcTE4IDAgMzAtMTNxMTMtMTMgMTMtMzB0LTEzLTMwem0tMjIyLTEzcS02MiAwLTEwNS41LTQzLjVUNDMgMTkyVDg2LjUgODYuNVQxOTIgNDN0MTA1LjUgNDMuNVQzNDEgMTkydC00My41IDEwNS41VDE5MiAzNDF6Ii8%2BCjwvc3ZnPg%3D%3D"; + + const embedCode = `Search with SemHub`; + + const copyToClipboard = () => { + navigator.clipboard.writeText(embedCode).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + }; + + return ( + + + + + +
+
+
Add to your repo
+

+ Add semantic search to your repository by embedding this badge in + your README and loading your repo into SemHub. +

+
+ + Search with SemHub + +
+
+
+
+                  {embedCode}
+                
+ +
+
+
+
+
+
+ ); +} diff --git a/packages/web/src/routes/r/$owner/$repo.tsx b/packages/web/src/routes/r/$owner/$repo.tsx index 0215f6a3..9bf79aa6 100644 --- a/packages/web/src/routes/r/$owner/$repo.tsx +++ b/packages/web/src/routes/r/$owner/$repo.tsx @@ -1,49 +1,18 @@ import { createFileRoute } from "@tanstack/react-router"; -import { CheckIcon, CopyIcon, InfoIcon, PlusIcon } from "lucide-react"; -import { useState } from "react"; import { useRepoStatus } from "@/lib/hooks/useRepo"; -import { getTimeAgo } from "@/lib/time"; -import { Button } from "@/components/ui/button"; -import { FastTooltip } from "@/components/ui/fast-tooltip"; import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { TooltipProvider } from "@/components/ui/tooltip"; + EmbedBadgePopover, + RepoStatusTooltip, +} from "@/components/search/repo/components"; import { RepoSearchBar } from "@/components/search/RepoSearchBar"; export const Route = createFileRoute("/r/$owner/$repo")({ component: RepoSearch, }); -function TimeDisplay({ label, date }: { label: string; date: string | null }) { - if (!date) return null; - const dateObj = new Date(date); - return ( - - {label}: {getTimeAgo(dateObj)} - - ); -} - function RepoSearch() { const { owner, repo } = Route.useParams(); - const [copied, setCopied] = useState(false); - - const imgSrc = - "https://img.shields.io/badge/search-semhub-blue?style=flat&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI%2BCiAgPHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNNDE0IDM1NHEtMTgtMTgtNDEtMTFsLTMyLTMycTQzLTUzIDQzLTExOXEwLTgwLTU2LTEzNlQxOTIgMFQ1NiA1NlQwIDE5MnQ1NiAxMzZ0MTM2IDU2cTcwIDAgMTE5LTQzbDMyIDMycS02IDI0IDExIDQxbDg1IDg1cTEzIDEzIDMwIDEzcTE4IDAgMzAtMTNxMTMtMTMgMTMtMzB0LTEzLTMwem0tMjIyLTEzcS02MiAwLTEwNS41LTQzLjVUNDMgMTkyVDg2LjUgODYuNVQxOTIgNDN0MTA1LjUgNDMuNVQzNDEgMTkydC00My41IDEwNS41VDE5MiAzNDF6Ii8%2BCjwvc3ZnPg%3D%3D"; - - const embedCode = `Search with SemHub`; - - const copyToClipboard = () => { - navigator.clipboard.writeText(embedCode).then(() => { - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }); - }; - const { data: repoStatus } = useRepoStatus(owner, repo); if (!repoStatus) { @@ -100,71 +69,11 @@ function RepoSearch() {
- - - - -
- } - > - - - - - - - - - -
-
-
Add to your repo
-

- Add semantic search to your repository by embedding this badge - in your README and loading your repo into SemHub. -

-
- Search with SemHub -
-
-
-
-                      {embedCode}
-                    
- -
-
-
-
-
-
+ + ); From 026ab0827e1ca1bbdf39516ecbf231f48e15c652 Mon Sep 17 00:00:00 2001 From: zx <67887489+tan-zx@users.noreply.github.com> Date: Fri, 17 Jan 2025 23:42:55 +0800 Subject: [PATCH 08/15] add syncstatus --- .../src/components/search/repo/components.tsx | 18 ++++++++++++++++++ packages/web/src/routes/r/$owner/$repo.tsx | 9 ++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/web/src/components/search/repo/components.tsx b/packages/web/src/components/search/repo/components.tsx index 6cf8530c..b92d09bd 100644 --- a/packages/web/src/components/search/repo/components.tsx +++ b/packages/web/src/components/search/repo/components.tsx @@ -22,18 +22,36 @@ function TimeDisplay({ label, date }: { label: string; date: string | null }) { ); } +function StatusDisplay({ + label, + status, +}: { + label: string; + status: string | null; +}) { + if (!status) return null; + return ( + + {label}: {status.charAt(0).toUpperCase() + status.slice(1).toLowerCase()} + + ); +} + export function RepoStatusTooltip({ lastSyncedAt, issuesLastUpdatedAt, + syncStatus, }: { lastSyncedAt: string | null; issuesLastUpdatedAt: string | null; + syncStatus: string | null; }) { return ( + diff --git a/packages/web/src/routes/r/$owner/$repo.tsx b/packages/web/src/routes/r/$owner/$repo.tsx index 9bf79aa6..4ae849dd 100644 --- a/packages/web/src/routes/r/$owner/$repo.tsx +++ b/packages/web/src/routes/r/$owner/$repo.tsx @@ -32,7 +32,13 @@ function RepoSearch() { ); } - const { avatarUrl, issuesLastUpdatedAt, lastSyncedAt } = repoStatus; + const { + avatarUrl, + issuesLastUpdatedAt, + lastSyncedAt, + initStatus, + syncStatus, + } = repoStatus; return (
@@ -72,6 +78,7 @@ function RepoSearch() {
From 60e97114bcee093a75e6cbc9219550ef3acc8498 Mon Sep 17 00:00:00 2001 From: zx <67887489+tan-zx@users.noreply.github.com> Date: Fri, 17 Jan 2025 23:51:19 +0800 Subject: [PATCH 09/15] add initstatus --- .../src/components/search/repo/components.tsx | 34 +++-- packages/web/src/lib/hooks/useRepo.ts | 2 + packages/web/src/routes/r/$owner/$repo.tsx | 143 +++++++++++------- 3 files changed, 109 insertions(+), 70 deletions(-) diff --git a/packages/web/src/components/search/repo/components.tsx b/packages/web/src/components/search/repo/components.tsx index b92d09bd..4946dd85 100644 --- a/packages/web/src/components/search/repo/components.tsx +++ b/packages/web/src/components/search/repo/components.tsx @@ -1,6 +1,7 @@ import { CheckIcon, CopyIcon, InfoIcon, PlusIcon } from "lucide-react"; import { useState } from "react"; +import { RepoStatus } from "@/lib/hooks/useRepo"; import { getTimeAgo } from "@/lib/time"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; @@ -23,35 +24,40 @@ function TimeDisplay({ label, date }: { label: string; date: string | null }) { } function StatusDisplay({ - label, - status, + initStatus, + syncStatus, }: { - label: string; - status: string | null; + initStatus: RepoStatus["initStatus"]; + syncStatus: RepoStatus["syncStatus"]; }) { - if (!status) return null; - return ( - - {label}: {status.charAt(0).toUpperCase() + status.slice(1).toLowerCase()} - - ); + let displayText = ""; + + if (initStatus && initStatus !== "completed") { + displayText = `Initialization: ${initStatus.charAt(0).toUpperCase() + initStatus.slice(1).toLowerCase()}`; + } else if (syncStatus) { + displayText = `Status: ${syncStatus.charAt(0).toUpperCase() + syncStatus.slice(1).toLowerCase()}`; + } + + return displayText ? {displayText} : null; } export function RepoStatusTooltip({ lastSyncedAt, issuesLastUpdatedAt, syncStatus, + initStatus, }: { - lastSyncedAt: string | null; - issuesLastUpdatedAt: string | null; - syncStatus: string | null; + lastSyncedAt: RepoStatus["lastSyncedAt"]; + issuesLastUpdatedAt: RepoStatus["issuesLastUpdatedAt"]; + syncStatus: RepoStatus["syncStatus"]; + initStatus: RepoStatus["initStatus"]; }) { return ( - + diff --git a/packages/web/src/lib/hooks/useRepo.ts b/packages/web/src/lib/hooks/useRepo.ts index 9de52bd1..cc21959f 100644 --- a/packages/web/src/lib/hooks/useRepo.ts +++ b/packages/web/src/lib/hooks/useRepo.ts @@ -111,3 +111,5 @@ export const useRepoStatus = (owner: string, repo: string) => { queryFn: () => getRepoStatus(owner, repo), }); }; + +export type RepoStatus = NonNullable["data"]>; diff --git a/packages/web/src/routes/r/$owner/$repo.tsx b/packages/web/src/routes/r/$owner/$repo.tsx index 4ae849dd..9c9613bd 100644 --- a/packages/web/src/routes/r/$owner/$repo.tsx +++ b/packages/web/src/routes/r/$owner/$repo.tsx @@ -15,23 +15,26 @@ function RepoSearch() { const { owner, repo } = Route.useParams(); const { data: repoStatus } = useRepoStatus(owner, repo); + const NotFoundView = () => ( +
+

Repository Not Found

+

+ The repository{" "} + + {owner}/{repo} + {" "} + could not be found. +

+

+ Please ensure this repository has been added to Semhub. +

+
+ ); + if (!repoStatus) { - return ( -
-

Repository Not Found

-

- The repository{" "} - - {owner}/{repo} - {" "} - could not be found. -

-

- Please ensure this repository has been added to Semhub. -

-
- ); + return ; } + const { avatarUrl, issuesLastUpdatedAt, @@ -40,48 +43,76 @@ function RepoSearch() { syncStatus, } = repoStatus; - return ( -
-
-
-
- {`${owner}'s -

- - - {owner}/{repo} - - -

+ const InitializingView = () => ( +
+

Repository Initializing

+

+ The repository{" "} + + {owner}/{repo} + {" "} + is being initialized. +

+

+ Please come back again later when the repository has been initialized. +

+
+ ); + + switch (initStatus) { + case "pending": + return ; + case "ready": + case "in_progress": + return ; + case "error": + case "completed": + return ( +
+
+
+ +

+ Uncover insights with{" "} + Sem + antic search for Git + Hub +

+
+ +
+
-

- Uncover insights with{" "} - Semantic - search for Git - Hub -

-
- +
+ +
-
-
- - -
-
- ); + ); + default: + return ; + } } From 9c54c792ae104a0b027b7dbfecfce7b536a14cfb Mon Sep 17 00:00:00 2001 From: zx <67887489+tan-zx@users.noreply.github.com> Date: Fri, 17 Jan 2025 23:55:48 +0800 Subject: [PATCH 10/15] add skeleton --- packages/web/src/routes/r/$owner/$repo.tsx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/web/src/routes/r/$owner/$repo.tsx b/packages/web/src/routes/r/$owner/$repo.tsx index 9c9613bd..7449567d 100644 --- a/packages/web/src/routes/r/$owner/$repo.tsx +++ b/packages/web/src/routes/r/$owner/$repo.tsx @@ -1,6 +1,7 @@ import { createFileRoute } from "@tanstack/react-router"; import { useRepoStatus } from "@/lib/hooks/useRepo"; +import { Skeleton } from "@/components/ui/skeleton"; import { EmbedBadgePopover, RepoStatusTooltip, @@ -9,8 +10,28 @@ import { RepoSearchBar } from "@/components/search/RepoSearchBar"; export const Route = createFileRoute("/r/$owner/$repo")({ component: RepoSearch, + pendingComponent: () => , }); +function RepoSearchSkeleton() { + return ( +
+
+
+
+ + +
+ +
+ +
+
+
+
+ ); +} + function RepoSearch() { const { owner, repo } = Route.useParams(); const { data: repoStatus } = useRepoStatus(owner, repo); @@ -113,6 +134,7 @@ function RepoSearch() {
); default: + initStatus satisfies never; return ; } } From 0b1f7b76da4c72c35bd25ee8721e892c95d55a9a Mon Sep 17 00:00:00 2001 From: zx <67887489+tan-zx@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:11:06 +0800 Subject: [PATCH 11/15] make easter egg dark mode logo more subtle --- .../src/components/navbar/DarkModeToggle.tsx | 18 +++++++- packages/web/src/components/navbar/Navbar.tsx | 43 +++++++++++++------ 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/packages/web/src/components/navbar/DarkModeToggle.tsx b/packages/web/src/components/navbar/DarkModeToggle.tsx index 601917e4..53f6fde1 100644 --- a/packages/web/src/components/navbar/DarkModeToggle.tsx +++ b/packages/web/src/components/navbar/DarkModeToggle.tsx @@ -1,11 +1,25 @@ +import { useState } from "react"; + import { useThemeToggle } from "@/lib/hooks/useThemeToggle"; import { Button } from "@/components/ui/button"; -export function DarkModeToggle() { +interface DarkModeToggleProps { + onToggleCountChange?: (count: number) => void; +} + +export function DarkModeToggle({ onToggleCountChange }: DarkModeToggleProps) { const { ThemeIcon, handleThemeChange } = useThemeToggle(); + const [toggleCount, setToggleCount] = useState(0); + + const handleClick = () => { + const newCount = toggleCount + 1; + setToggleCount(newCount); + onToggleCountChange?.(newCount); + handleThemeChange(); + }; return ( - diff --git a/packages/web/src/components/navbar/Navbar.tsx b/packages/web/src/components/navbar/Navbar.tsx index 3eb09d30..0cdd1a91 100644 --- a/packages/web/src/components/navbar/Navbar.tsx +++ b/packages/web/src/components/navbar/Navbar.tsx @@ -1,4 +1,5 @@ import { Link } from "@tanstack/react-router"; +import { useState } from "react"; import { useSession } from "@/lib/hooks/useSession"; import { DarkModeToggle } from "@/components/navbar/DarkModeToggle"; @@ -7,22 +8,40 @@ import { UserNav } from "@/components/navbar/UserNav"; export function Navbar() { const { isAuthenticated, user } = useSession(); + const [toggleCount, setToggleCount] = useState(0); + const showEasterEgg = toggleCount >= 8; + const standardLogo = ( + <> + Sem + Hub + _ + + ); + const lightModeLogo = ( +

+ {standardLogo} +

+ ); + const darkModeLogo = ( +

+ {standardLogo} +

+ ); + const darkModeEasterEggLogo = ( +

+ Sem + + Hub + +

+ ); return (
-

- Sem - Hub - _ -

-

- Sem - - Hub - -

+ {lightModeLogo} + {showEasterEgg ? darkModeEasterEggLogo : darkModeLogo}
From ff3c2361e7c95b272434bd2b7559ed2e06a96d20 Mon Sep 17 00:00:00 2001 From: zx <67887489+tan-zx@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:15:46 +0800 Subject: [PATCH 12/15] bring back audio hehe This reverts commit 1c8b6820fcc497a957929222b108a04e45d8f8fc. --- packages/web/public/sounds/dark-mode.mp3 | Bin 0 -> 38015 bytes packages/web/src/components/navbar/Navbar.tsx | 24 +++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 packages/web/public/sounds/dark-mode.mp3 diff --git a/packages/web/public/sounds/dark-mode.mp3 b/packages/web/public/sounds/dark-mode.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..da1aa08a686196c2cd02f3bd183e3497dba892d5 GIT binary patch literal 38015 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 XFaElSE+sww|Nq;=zc~H>bJ_m_94+#v literal 0 HcmV?d00001 diff --git a/packages/web/src/components/navbar/Navbar.tsx b/packages/web/src/components/navbar/Navbar.tsx index 0cdd1a91..a6779a14 100644 --- a/packages/web/src/components/navbar/Navbar.tsx +++ b/packages/web/src/components/navbar/Navbar.tsx @@ -1,7 +1,9 @@ import { Link } from "@tanstack/react-router"; -import { useState } from "react"; +import { useTheme } from "next-themes"; +import { useEffect, useRef, useState } from "react"; import { useSession } from "@/lib/hooks/useSession"; +import { useAudio } from "@/hooks/useAudio"; import { DarkModeToggle } from "@/components/navbar/DarkModeToggle"; import { LoginButton } from "@/components/navbar/LoginButton"; import { UserNav } from "@/components/navbar/UserNav"; @@ -9,8 +11,28 @@ import { UserNav } from "@/components/navbar/UserNav"; export function Navbar() { const { isAuthenticated, user } = useSession(); const [toggleCount, setToggleCount] = useState(0); + const { theme, systemTheme } = useTheme(); + const audio = useAudio("/sounds/dark-mode.mp3"); + const prevThemeRef = useRef(theme); + const prevSystemThemeRef = useRef(systemTheme); const showEasterEgg = toggleCount >= 8; + useEffect(() => { + const wasInDarkMode = + prevThemeRef.current === "dark" || + (prevThemeRef.current === "system" && + prevSystemThemeRef.current === "dark"); + const isInDarkMode = + theme === "dark" || (theme === "system" && systemTheme === "dark"); + + if (!wasInDarkMode && isInDarkMode && showEasterEgg) { + audio.play().catch(console.error); + } + + prevThemeRef.current = theme; + prevSystemThemeRef.current = systemTheme; + }, [theme, systemTheme, audio, showEasterEgg]); + const standardLogo = ( <> Sem From 855f5d816f54dc0a6e8b341453d85d7f162e6aba Mon Sep 17 00:00:00 2001 From: zx <67887489+tan-zx@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:27:44 +0800 Subject: [PATCH 13/15] simplify --- packages/web/src/components/navbar/DarkModeToggle.tsx | 9 ++------- packages/web/src/components/navbar/Navbar.tsx | 6 +++++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/web/src/components/navbar/DarkModeToggle.tsx b/packages/web/src/components/navbar/DarkModeToggle.tsx index 53f6fde1..b1e9d395 100644 --- a/packages/web/src/components/navbar/DarkModeToggle.tsx +++ b/packages/web/src/components/navbar/DarkModeToggle.tsx @@ -1,20 +1,15 @@ -import { useState } from "react"; - import { useThemeToggle } from "@/lib/hooks/useThemeToggle"; import { Button } from "@/components/ui/button"; interface DarkModeToggleProps { - onToggleCountChange?: (count: number) => void; + onToggleCountChange: () => void; } export function DarkModeToggle({ onToggleCountChange }: DarkModeToggleProps) { const { ThemeIcon, handleThemeChange } = useThemeToggle(); - const [toggleCount, setToggleCount] = useState(0); const handleClick = () => { - const newCount = toggleCount + 1; - setToggleCount(newCount); - onToggleCountChange?.(newCount); + onToggleCountChange(); handleThemeChange(); }; diff --git a/packages/web/src/components/navbar/Navbar.tsx b/packages/web/src/components/navbar/Navbar.tsx index a6779a14..e79ab589 100644 --- a/packages/web/src/components/navbar/Navbar.tsx +++ b/packages/web/src/components/navbar/Navbar.tsx @@ -33,6 +33,10 @@ export function Navbar() { prevSystemThemeRef.current = systemTheme; }, [theme, systemTheme, audio, showEasterEgg]); + const handleToggleCount = () => { + setToggleCount((prev) => prev + 1); + }; + const standardLogo = ( <> Sem @@ -80,7 +84,7 @@ export function Navbar() { ) : ( <> - + )} From bdf6a7343a95e5879e71fb6612b60072b22bbade Mon Sep 17 00:00:00 2001 From: zx <67887489+tan-zx@users.noreply.github.com> Date: Sat, 18 Jan 2025 01:12:57 +0800 Subject: [PATCH 14/15] filter out operators --- .../src/components/search/MeSearchBars.tsx | 6 ++-- .../components/search/PublicSearchBars.tsx | 11 ++++++-- .../src/components/search/RepoSearchBar.tsx | 7 ++++- .../components/search/SearchDropdownMenu.tsx | 17 ++++++++--- packages/web/src/hooks/useSearchBar.ts | 28 +++++++++++++++---- 5 files changed, 55 insertions(+), 14 deletions(-) diff --git a/packages/web/src/components/search/MeSearchBars.tsx b/packages/web/src/components/search/MeSearchBars.tsx index 22f78951..5a677585 100644 --- a/packages/web/src/components/search/MeSearchBars.tsx +++ b/packages/web/src/components/search/MeSearchBars.tsx @@ -29,7 +29,9 @@ export function MyReposResultsSearchBar({ handleBlur, commandValue, setCommandValue, - } = useSearchBar(initialQuery); + } = useSearchBar({ + initialQuery, + }); const { handleSearch } = useMeSearch(setQuery); return ( @@ -104,7 +106,7 @@ export function MyReposSearchBar() { commandValue, setCommandValue, setQuery, - } = useSearchBar(); + } = useSearchBar({}); const { handleSearch } = useMeSearch(setQuery); return ( diff --git a/packages/web/src/components/search/PublicSearchBars.tsx b/packages/web/src/components/search/PublicSearchBars.tsx index 8205a2ce..6f095998 100644 --- a/packages/web/src/components/search/PublicSearchBars.tsx +++ b/packages/web/src/components/search/PublicSearchBars.tsx @@ -220,6 +220,7 @@ function SearchFilters({ export function ResultsSearchBar({ query: initialQuery }: { query: string }) { const [selectedOrg, setSelectedOrg] = useState("all"); const [selectedRepo, setSelectedRepo] = useState("all"); + const removedOperators = ["collection"] as SearchOperator[]; const { query, inputRef, @@ -238,7 +239,10 @@ export function ResultsSearchBar({ query: initialQuery }: { query: string }) { commandValue, setCommandValue, setQuery, - } = useSearchBar(initialQuery); + } = useSearchBar({ + initialQuery, + removedOperators, + }); const { handleSearch } = usePublicSearch({ mode: "search", setQuery }); const handleOrgChange = (org: string) => { @@ -306,6 +310,7 @@ export function ResultsSearchBar({ query: initialQuery }: { query: string }) { handleValueSelect={handleValueSelect} commandValue={commandValue} setCommandValue={setCommandValue} + removedOperators={removedOperators} />
)} @@ -318,6 +323,7 @@ export function HomepageSearchBar() { const { theme } = useTheme(); const [selectedOrg, setSelectedOrg] = useState("all"); const [selectedRepo, setSelectedRepo] = useState("all"); + const removedOperators = ["collection"] as SearchOperator[]; const { query, inputRef, @@ -336,7 +342,7 @@ export function HomepageSearchBar() { commandValue, setCommandValue, setQuery, - } = useSearchBar(); + } = useSearchBar({ removedOperators }); const { handleSearch, handleLuckySearch } = usePublicSearch({ mode: "search", setQuery, @@ -404,6 +410,7 @@ export function HomepageSearchBar() { handleValueSelect={handleValueSelect} commandValue={commandValue} setCommandValue={setCommandValue} + removedOperators={removedOperators} />
)} diff --git a/packages/web/src/components/search/RepoSearchBar.tsx b/packages/web/src/components/search/RepoSearchBar.tsx index e3077c51..4b799110 100644 --- a/packages/web/src/components/search/RepoSearchBar.tsx +++ b/packages/web/src/components/search/RepoSearchBar.tsx @@ -1,6 +1,7 @@ import { SearchIcon, XIcon } from "lucide-react"; import { useTheme } from "next-themes"; +import { SearchOperator } from "@/core/constants/search.constant"; import { usePublicSearch } from "@/hooks/usePublicSearch"; import { useSearchBar } from "@/hooks/useSearchBar"; import { Button } from "@/components/ui/button"; @@ -14,6 +15,7 @@ interface RepoSearchBarProps { export function RepoSearchBar({ owner, repo }: RepoSearchBarProps) { const { theme } = useTheme(); + const removedOperators = ["org", "repo", "collection"] as SearchOperator[]; const { query, inputRef, @@ -32,7 +34,9 @@ export function RepoSearchBar({ owner, repo }: RepoSearchBarProps) { commandValue, setCommandValue, setQuery, - } = useSearchBar(); + } = useSearchBar({ + removedOperators, + }); const { handleSearch, handleLuckySearch } = usePublicSearch({ mode: "repo_search", setQuery, @@ -82,6 +86,7 @@ export function RepoSearchBar({ owner, repo }: RepoSearchBarProps) { handleValueSelect={handleValueSelect} commandValue={commandValue} setCommandValue={setCommandValue} + removedOperators={removedOperators} />
)} diff --git a/packages/web/src/components/search/SearchDropdownMenu.tsx b/packages/web/src/components/search/SearchDropdownMenu.tsx index 9aa1346f..4a17ed02 100644 --- a/packages/web/src/components/search/SearchDropdownMenu.tsx +++ b/packages/web/src/components/search/SearchDropdownMenu.tsx @@ -23,6 +23,7 @@ import { } from "@/components/ui/command"; interface SearchDropdownMenuProps { + removedOperators?: SearchOperator[]; commandRef: React.RefObject; commandInputRef: React.RefObject; commandInputValue: string; @@ -41,11 +42,13 @@ const preventDefault = (e: React.MouseEvent | React.TouchEvent) => { function OperatorItems({ commandInputValue, onSelect, + removedOperators, }: { commandInputValue: string; onSelect: (operator: OperatorWithIcon) => void; + removedOperators: SearchOperator[]; }) { - return getFilteredOperators(commandInputValue).map((o) => ( + return getFilteredOperators(commandInputValue, removedOperators).map((o) => ( onSelect(o)} @@ -89,6 +92,7 @@ export function SearchDropdownMenu({ handleValueSelect, commandValue, setCommandValue, + removedOperators = [], }: SearchDropdownMenuProps) { return ( )} {subMenu === "state" && handleValueSelect && ( @@ -192,11 +197,15 @@ export const OPERATOR_SUBMENU_VALUES = new Map([ ], ]); -export function getFilteredOperators(word: string) { +export function getFilteredOperators( + word: string, + removedOperators: SearchOperator[], +) { return OPERATORS_WITH_ICONS.filter( (o) => - o.operator.toLowerCase().startsWith(word.toLowerCase()) || - o.name.toLowerCase().startsWith(word.toLowerCase()), + !removedOperators.includes(o.operator) && + (o.operator.toLowerCase().startsWith(word.toLowerCase()) || + o.name.toLowerCase().startsWith(word.toLowerCase())), ); } diff --git a/packages/web/src/hooks/useSearchBar.ts b/packages/web/src/hooks/useSearchBar.ts index c02c42b4..9459e570 100644 --- a/packages/web/src/hooks/useSearchBar.ts +++ b/packages/web/src/hooks/useSearchBar.ts @@ -1,6 +1,9 @@ import { useEffect, useMemo, useRef, useState } from "react"; -import { SEARCH_OPERATORS } from "@/core/constants/search.constant"; +import { + SEARCH_OPERATORS, + type SearchOperator, +} from "@/core/constants/search.constant"; import type { OperatorWithIcon, SubmenuValue, @@ -81,7 +84,15 @@ const getValSelectCursorPosition = ( return cursorPosition - commandInputValue.length + value.length; }; -export function useSearchBar(initialQuery = "") { +interface UseSearchBarProps { + initialQuery?: string; + removedOperators?: SearchOperator[]; +} + +export function useSearchBar({ + initialQuery = "", + removedOperators = [], +}: UseSearchBarProps) { const [query, setQuery] = useState(initialQuery); const commandInputRef = useRef(null); const commandRef = useRef(null); @@ -95,6 +106,12 @@ export function useSearchBar(initialQuery = "") { const defaultCommandValue = "__no_selection__"; const [commandValue, setCommandValue] = useState(defaultCommandValue); + const searchOperators = useMemo(() => { + return SEARCH_OPERATORS.filter( + (op) => !removedOperators.includes(op.operator), + ); + }, [removedOperators]); + const cursorWord = useMemo(() => { return getCursorWord(query, cursorPosition).cursorWord; }, [query, cursorPosition]); @@ -103,7 +120,7 @@ export function useSearchBar(initialQuery = "") { if (!cursorWord || !cursorWord.includes(":")) return null; const op = cursorWord.slice(0, cursorWord.indexOf(":")).toLowerCase(); const val = cursorWord.slice(cursorWord.indexOf(":") + 1).toLowerCase(); - const matchedOp = SEARCH_OPERATORS.find( + const matchedOp = searchOperators.find( ({ operator }) => operator === op, )?.operator; // user has not fully typed an operator, show main menu @@ -115,7 +132,7 @@ export function useSearchBar(initialQuery = "") { if (!matchedVal) return matchedOp; // finally, revert to main menu when operator's submenu value is fully typed return null; - }, [cursorWord]); + }, [cursorWord, searchOperators]); const commandInputValue = useMemo(() => { // default menu to show @@ -126,7 +143,8 @@ export function useSearchBar(initialQuery = "") { const shouldShowDropdown = useMemo(() => { const showOperatorList = - !subMenu && getFilteredOperators(commandInputValue).length > 0; + !subMenu && + getFilteredOperators(commandInputValue, removedOperators).length > 0; const showSubmenuList = subMenu && getFilteredSubmenuValues(commandInputValue, subMenu).length > 0; From 8fa6863eda335ea03a645bc51116e75febc7238a Mon Sep 17 00:00:00 2001 From: zx <67887489+tan-zx@users.noreply.github.com> Date: Sat, 18 Jan 2025 01:17:16 +0800 Subject: [PATCH 15/15] standardise spelling --- README.md | 4 ++-- packages/core/migrations/0033_vector-plain-storage.sql | 2 +- packages/core/src/email/index.ts | 2 +- packages/core/src/github/permission/github-app.ts | 2 +- packages/web/src/hooks/usePlaceholderAnimation.ts | 4 ++-- packages/web/src/routes/r/$owner/$repo.tsx | 2 +- packages/web/src/routes/repos/index.tsx | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 868b1543..b5a54888 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Semhub +# SemHub ## Development @@ -84,7 +84,7 @@ To set up a GitHub App: - In terms of permissions: - Select the following read-only Repository permissions: Metadata (mandatory), Discussions, Issues, Pull Requests, Contents. (These should be tracked in code via `github-app.ts`.) - Select the following read-only User permissions: Emails (actually would've gotten the user's email from the login process) - - Select the following read-only Organization permissions: Members (to enable Semhub to work for users in the same organization after it has been installed by an admin) + - Select the following read-only Organization permissions: Members (to enable SemHub to work for users in the same organization after it has been installed by an admin) - Leave unchecked the box that says "Request user authorization (OAuth) during installation". Our app handles user login + creation. - Select redirect on update and use the frontend `/repos` page as the Setup URL - Local dev: `https://local.semhub.dev:3001/repos` diff --git a/packages/core/migrations/0033_vector-plain-storage.sql b/packages/core/migrations/0033_vector-plain-storage.sql index bddbfb0e..85532f0f 100644 --- a/packages/core/migrations/0033_vector-plain-storage.sql +++ b/packages/core/migrations/0033_vector-plain-storage.sql @@ -1,7 +1,7 @@ /* pg (or pgvector?) uses EXTENDED storage by default for for all columns bigger than 2KB -Changes vector storage to PLAIN if vector operation is in hot path (which it is for Semhub): +Changes vector storage to PLAIN if vector operation is in hot path (which it is for SemHub): - this only works if your row size is within PG page limit of 8KB - you cannot increase page limit beyond 8KB for index diff --git a/packages/core/src/email/index.ts b/packages/core/src/email/index.ts index 6af52153..d4b4baaf 100644 --- a/packages/core/src/email/index.ts +++ b/packages/core/src/email/index.ts @@ -18,7 +18,7 @@ export async function sendEmail( envPrefix: string, ) { const { data, error } = await client.emails.send({ - from: `${envPrefix ? `${envPrefix} ` : ""}Semhub `, + from: `${envPrefix ? `${envPrefix} ` : ""}SemHub `, to, subject, html, diff --git a/packages/core/src/github/permission/github-app.ts b/packages/core/src/github/permission/github-app.ts index 228b9d03..54b6824a 100644 --- a/packages/core/src/github/permission/github-app.ts +++ b/packages/core/src/github/permission/github-app.ts @@ -1,6 +1,6 @@ import { type Permissions } from "../schema.webhook"; -// this file tracks the current permissions requested by Semhub Github App +// this file tracks the current permissions requested by SemHub Github App // however, the actual permissions are configured via UI on GitHub export const CURRENT_REQUESTED_PERMISSIONS: Permissions = { diff --git a/packages/web/src/hooks/usePlaceholderAnimation.ts b/packages/web/src/hooks/usePlaceholderAnimation.ts index c20e6c6f..c7ad0ee9 100644 --- a/packages/web/src/hooks/usePlaceholderAnimation.ts +++ b/packages/web/src/hooks/usePlaceholderAnimation.ts @@ -2,10 +2,10 @@ import { useEffect, useState } from "react"; // need to be short for entire text to fit in mobile const PLACEHOLDER_TEXTS = [ - "Semhub understands what you mean", + "SemHub understands what you mean", "Search issues across multiple repos", "Login to customize your search", - "Add Semhub Search to your repo", + "Add SemHub search to your repo", ] as const; const TYPING_DELAY_MS = 20; diff --git a/packages/web/src/routes/r/$owner/$repo.tsx b/packages/web/src/routes/r/$owner/$repo.tsx index 7449567d..074f1439 100644 --- a/packages/web/src/routes/r/$owner/$repo.tsx +++ b/packages/web/src/routes/r/$owner/$repo.tsx @@ -47,7 +47,7 @@ function RepoSearch() { could not be found.

- Please ensure this repository has been added to Semhub. + Please ensure this repository has been added to SemHub.

); diff --git a/packages/web/src/routes/repos/index.tsx b/packages/web/src/routes/repos/index.tsx index c4bb3b0d..cf2308a8 100644 --- a/packages/web/src/routes/repos/index.tsx +++ b/packages/web/src/routes/repos/index.tsx @@ -88,7 +88,7 @@ function RepoSection({ title, type, repos }: RepoSectionProps) { Want to add your private repos? - Click “Authorize” to grant Semhub access to your private + Click “Authorize” to grant SemHub access to your private repositories.