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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
- Runtime: Node 24 through `mise exec node@24 --`; pnpm 11.2.1 through Corepack.
- Install with `pnpm install`, create the documented `.env` symlinks, then run `pnpm db:migrate`.
- Start local development with `pnpm dev:start`. Use `pnpm dev:start -d` for detached mode and `pnpm dev:stop` to stop only that workspace.
- Shared local infrastructure is machine-level: one Meilisearch at `http://localhost:7700` and one Chrome/CDP at `http://localhost:9222`. Manage it explicitly with `pnpm dev:infra:up`, `pnpm dev:infra:status`, and `pnpm dev:infra:down`.
- Shared local infrastructure is machine-level: one Meilisearch at `http://localhost:7700` and one Chrome/CDP at the configured development port, `9250` by default. Override it with `MARKA_DEV_CHROME_PORT`.
- Parallel worktrees keep separate SQLite/assets data and unique web ports. `scripts/setup-worktree.sh` assigns each worktree a unique `MEILI_INDEX_PREFIX`; both `bookmarks` and `bookmarks_vectors` use that namespace on the shared Meilisearch server.
- `pnpm dev:start` defaults the main workspace namespace to `main_`. An unset `MEILI_INDEX_PREFIX` is a compatibility fallback for the original `bookmarks` and `bookmarks_vectors` names; manual `web` or `workers` starts outside `pnpm dev:start` must set an explicit unique prefix.
- Run focused checks before broad checks when practical. Standard checks are `pnpm format:fix`, `pnpm lint`, `pnpm typecheck`, and `pnpm test`.
Expand Down Expand Up @@ -126,7 +126,7 @@ Useful variants:
Local-dev ownership model:
- `web` + `workers` run natively per workspace
- one machine-level Meilisearch container is shared at `http://localhost:7700`
- one machine-level Chrome container is shared at `http://localhost:9222`
- one machine-level Chrome container is shared at `http://localhost:9250` by default; `MARKA_DEV_CHROME_PORT` changes this endpoint.
- `pnpm dev:start` automatically ensures those shared containers exist
- `pnpm dev:stop` never stops shared infrastructure because other worktrees may still use it
- the shared Chrome image is `ghcr.io/karakeep-app/karakeep-chrome:release`
Expand All @@ -152,7 +152,7 @@ pnpm workers

Notes:
- Meilisearch and headless Chrome are optional for booting the app, but required for full search/crawling behavior.
- shared infra binds only to localhost; if ports `7700` or `9222` are occupied by something else, the helper fails rather than silently reusing an unknown service
- shared infra binds only to localhost; if the configured ports `7700` or `MARKA_DEV_CHROME_PORT` are occupied by something else, the helper fails rather than silently reusing an unknown service
- If `next dev` crashes with a stale Turbopack/instrumentation issue, clear `apps/web/.next`.

### Pull prod state to local dev
Expand Down
2 changes: 1 addition & 1 deletion apps/browser-extension/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Karakeep</title>
<title>Marka</title>
</head>
<body>
<div id="root"></div>
Expand Down
6 changes: 3 additions & 3 deletions apps/browser-extension/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Karakeep",
"description": "An extension to bookmark links to karakeep.app",
"name": "Marka",
"description": "An extension to bookmark links to your Marka server",
"version": "1.2.11",
"icons": {
"16": "public/logo-16.png",
Expand Down Expand Up @@ -65,7 +65,7 @@
"suggested_key": {
"default": "Ctrl+Shift+E"
},
"description": "Send the current page URL to Karakeep."
"description": "Send the current page URL to Marka."
}
}
}
4 changes: 2 additions & 2 deletions apps/browser-extension/src/Logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import logoImg from "../public/logo-full.png";
export default function Logo() {
return (
<span className="flex items-center justify-center">
<img src={logoImg} alt="karakeep logo" className="h-14 dark:hidden" />
<img src={logoImg} alt="Marka logo" className="h-14 dark:hidden" />
<img
src={logoImgWhite}
alt="karakeep logo"
alt="Marka logo"
className="hidden h-14 dark:block"
/>
</span>
Expand Down
6 changes: 3 additions & 3 deletions apps/browser-extension/src/background/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,20 @@ function registerContextMenus(settings: Settings) {
removeContextMenus();
chrome.contextMenus.create({
id: OPEN_KARAKEEP_ID,
title: "Open Karakeep",
title: "Open Marka",
contexts: ["action"],
});

chrome.contextMenus.create({
id: ADD_LINK_TO_KARAKEEP_ID,
title: "Add to Karakeep",
title: "Add to Marka",
contexts: ["link", "page", "selection", "image"],
});

if (settings?.showCountBadge) {
chrome.contextMenus.create({
id: VIEW_PAGE_IN_KARAKEEP,
title: "View this page in Karakeep",
title: "View this page in Marka",
contexts: ["action", "page"],
});
if (settings?.useBadgeCache) {
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/package.json",
"name": "@karakeep/cli",
"version": "0.32.0",
"description": "Command Line Interface (CLI) for Karakeep",
"description": "Command Line Interface (CLI) for Marka",
"license": "GNU Affero General Public License version 3",
"type": "module",
"keywords": [
Expand Down
4 changes: 2 additions & 2 deletions apps/cli/src/commands/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@ authCmd
opts.serverAddr ??
(await promptForValue(
rl,
"Karakeep server address",
"Marka server address",
existingAuth.serverAddr ?? DEFAULT_SERVER_ADDR,
));
const apiKey =
opts.apiKey ??
(await promptForValue(rl, "Karakeep API key", existingAuth.apiKey));
(await promptForValue(rl, "Marka API key", existingAuth.apiKey));
rl.close();

if (!serverAddr || !apiKey) {
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/commands/dump.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export const dumpCmd = new Command()

try {
line("");
line(`${chalk.bold("Karakeep Dump")}`);
line(`${chalk.bold("Marka Dump")}`);
line(`${chalk.gray("Server:")} ${globals.serverAddr}`);
line(`${chalk.gray("Output:")} ${outFile}`);
line("");
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/commands/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export const migrateCmd = new Command()

try {
line("");
line(`${chalk.bold("Karakeep Migration")}`);
line(`${chalk.bold("Marka Migration")}`);
line(`${chalk.gray("From:")} ${globals.serverAddr}`);
line(`${chalk.gray("To: ")} ${opts.destServer}`);
line("");
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/commands/wipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export const wipeCmd = new Command()

try {
line("");
line(`${chalk.bold("Karakeep Wipe")}`);
line(`${chalk.bold("Marka Wipe")}`);
line(`${chalk.gray("Server:")} ${globals.serverAddr}`);
line("");

Expand Down
4 changes: 2 additions & 2 deletions apps/mcp/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Karakeep MCP server
# Marka MCP server

This package contains the MCP server for Karakeep.
This package contains the MCP server for Marka.

It exposes bookmark/list/tag operations for external MCP-compatible tools and agents.

Expand Down
2 changes: 1 addition & 1 deletion apps/mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/package.json",
"name": "@karakeep/mcp",
"version": "0.32.0",
"description": "MCP server for Karakeep",
"description": "MCP server for Marka",
"license": "GNU Affero General Public License version 3",
"type": "module",
"keywords": [
Expand Down
2 changes: 1 addition & 1 deletion apps/mcp/src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const karakeepClient = createKarakeepClient({
});

export const mcpServer = new McpServer({
name: "Karakeep",
name: "Marka",
version: "0.23.0",
});

Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ export default {
expo: {
...(IS_DEV
? {
name: "Karakeep (Dev)",
name: "Marka (Dev)",
scheme: "karakeep-dev",
}
: {
name: "Karakeep",
name: "Marka",
scheme: "karakeep",
}),
slug: "hoarder",
Expand Down
10 changes: 1 addition & 9 deletions apps/mobile/app/dashboard/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,7 @@ export default function TabLayout() {
<Label>Tags</Label>
</NativeTabs.Trigger>

<NativeTabs.Trigger name="(highlights)">
<Icon
sf="highlighter"
androidSrc={
<VectorIcon family={MaterialCommunityIcons} name="marker" />
}
/>
<Label>Highlights</Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name="(highlights)" hidden />

<NativeTabs.Trigger
name="(search)"
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/app/server-address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export default function ServerAddress() {
</Text>
<View className="w-full gap-3 rounded-lg bg-card px-4 py-4">
<Text className="text-sm text-muted-foreground">
Enter the URL of your Karakeep server
Enter the URL of your Marka server
</Text>
<Input
placeholder="https://cloud.karakeep.app"
Expand Down
2 changes: 1 addition & 1 deletion apps/web/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# `@karakeep/web`

This is the main Next.js web application for Karakeep.
This is the main Next.js web application for Marka.

## What lives here

Expand Down
8 changes: 8 additions & 0 deletions apps/web/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,14 @@
}
}

@media (max-width: 639px) {
/* Leave a quiet strip below bottom sheets for the phone gesture handle. */
.dialog-vv-bottom-safe-area {
background-color: var(--card) !important;
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 1.5rem) !important;
}
}

/* ============================================================
Mobile / touch / PWA ergonomics. Deliberately UNLAYERED: cascade layers
rank unlayered rules above any @layer, so the 16px form-control size below
Expand Down
32 changes: 30 additions & 2 deletions apps/web/components/dashboard/header/Header.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @vitest-environment jsdom

import React from "react";
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";

vi.mock("next/link", () => ({
default: ({ children, href, ...props }: React.ComponentProps<"a">) => (
Expand Down Expand Up @@ -35,14 +35,42 @@ vi.mock("@/server/auth", () => ({
import Header from "./Header";

describe("Header", () => {
afterEach(() => {
cleanup();
});

it("uses a left-aligned Marka wordmark in the original reserved logo slot", async () => {
render(await Header());

const header = screen.getByRole("banner");
const link = screen.getByRole("link", { name: "Marka" });
const search = screen.getByRole("textbox", { name: "Search" });
expect(link.getAttribute("href")).toBe("/dashboard/bookmarks");
expect(link.classList.contains("justify-start")).toBe(true);
expect(link.classList.contains("w-56")).toBe(true);
expect(link.classList.contains("pl-2")).toBe(true);
expect(screen.getByAltText("Marka").getAttribute("height")).toBe("30");
expect(header.firstElementChild?.className).toContain(
"lg:grid-cols-[minmax(0,1fr)_minmax(0,2fr)_minmax(0,1fr)]",
);
expect(search.parentElement?.className).toContain("lg:col-start-2");
});

it("groups the status rectangle and profile avatar with the avatar on top", async () => {
render(await Header());

const profile = screen.getByRole("button", { name: "Profile" });
const avatarLayer = profile.parentElement;
const controls = avatarLayer?.parentElement;

expect(avatarLayer?.className).toContain("absolute");
expect(avatarLayer?.className).toContain("right-0");
expect(avatarLayer?.className).toContain("top-1/2");
expect(avatarLayer?.className).toContain("-translate-y-1/2");
expect(avatarLayer?.className).toContain("size-10");
expect(avatarLayer?.className).toContain("z-10");
expect(controls?.className).toContain("relative");
expect(controls?.className).toContain("h-10");
expect(controls?.className).toContain("pr-5");
});
});
10 changes: 6 additions & 4 deletions apps/web/components/dashboard/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default async function Header() {

return (
<header className="bg-sidebar/95 sticky left-0 right-0 top-0 z-50">
<div className="flex h-16 items-center gap-3 overflow-x-auto overflow-y-hidden px-3 sm:px-4">
<div className="flex h-16 items-center gap-3 overflow-x-auto overflow-y-hidden px-3 sm:px-4 lg:grid lg:grid-cols-[minmax(0,1fr)_minmax(0,2fr)_minmax(0,1fr)]">
<div className="hidden shrink-0 items-center sm:flex">
<Link
href={"/dashboard/bookmarks"}
Expand All @@ -24,12 +24,14 @@ export default async function Header() {
<MarkaLogo height={30} />
</Link>
</div>
<div className="min-w-0 flex-1">
<div className="min-w-0 flex-1 lg:col-start-2 lg:row-start-1">
<SearchInput className="w-full" />
</div>
<div className="ml-3 flex shrink-0 items-center gap-2 sm:ml-4">
<div className="relative ml-3 flex h-10 shrink-0 items-center pr-5 sm:ml-4 lg:col-start-3 lg:row-start-1 lg:justify-self-end">
<ProcessingStatusIndicator />
<ProfileOptions />
<div className="absolute right-0 top-1/2 z-10 size-10 -translate-y-1/2">
<ProfileOptions />
</div>
</div>
</div>
</header>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ export default function ProcessingStatusIndicator() {
<Button
type="button"
variant="ghost"
className="shadow-xs ease-(--ease-out) h-10 gap-1.5 rounded-xl border border-border/70 bg-background px-2.5 text-foreground transition-[background-color,border-color,box-shadow] duration-150 hover:bg-accent/70"
className="shadow-xs ease-(--ease-out) h-10 min-w-16 justify-start gap-1.5 rounded-l-xl rounded-r-none border border-border/70 bg-background px-2.5 text-foreground transition-[background-color,border-color,box-shadow] duration-150 hover:bg-accent/70"
aria-label={buttonLabel}
>
<Icon
Expand Down
4 changes: 2 additions & 2 deletions apps/web/components/dashboard/header/ProfileOptions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ vi.mock("@/lib/i18n/client", () => ({
apps_extensions: "Apps & extensions",
coming_soon: "Coming soon",
documentation: "Documentation",
update_now: "Update now",
};
return profileTranslations[key] ?? key;
}
Expand Down Expand Up @@ -142,11 +143,10 @@ describe("ProfileOptions", () => {
expect(disabledItems).toHaveLength(2);
});

it("shows the shared build footer only in the mobile profile layout", () => {
it("shows the shared build footer in the profile menu", () => {
render(<ProfileOptions />);

const version = screen.getByTestId("sidebar-version");
expect(version.getAttribute("data-placement")).toBe("profile");
expect(version.parentElement?.className).toContain("sm:hidden");
});
});
9 changes: 3 additions & 6 deletions apps/web/components/dashboard/header/ProfileOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,13 @@ export default function SidebarProfileOptions() {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
className="shadow-xs ease-(--ease-out) aspect-square size-10 rounded-full border border-border/70 bg-background p-0 text-foreground transition-[background-color,border-color,box-shadow] duration-150 hover:bg-accent/70"
className="shadow-xs ease-(--ease-out) flex size-10 shrink-0 rounded-full border border-border/70 bg-background p-0 text-foreground transition-[background-color,border-color,box-shadow] duration-150 hover:bg-accent/70"
variant="ghost"
>
<UserAvatar
image={avatarUrl}
name={session.user.name}
className="h-full w-full rounded-full"
className="size-full rounded-full border border-border/70"
/>
</Button>
</DropdownMenuTrigger>
Expand Down Expand Up @@ -160,14 +160,11 @@ export default function SidebarProfileOptions() {
comingSoon={tProfile("coming_soon")}
/>
<Separator className="my-2" />
<div className="sm:hidden">
<SidebarVersion placement="profile" />
</div>
<Separator className="my-2 sm:hidden" />
<DropdownMenuItem onClick={() => router.push("/logout")}>
<LogOut className="mr-2 size-4" />
<span>{t("actions.sign_out")}</span>
</DropdownMenuItem>
<SidebarVersion placement="profile" />
</DropdownMenuContent>
</DropdownMenu>
);
Expand Down
Loading
Loading