Skip to content

Commit 2df02d2

Browse files
chore(release): v0.13.6
GitOrigin-RevId: 65eed9c5b3069a6d905d241695347187960e7100
1 parent aed0afc commit 2df02d2

12 files changed

Lines changed: 120 additions & 28 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
## [0.13.6] - 2026-08-18
6+
7+
- Fixed project favicons after hydration and kept Keyword Research controls within compact dashboard widths.
8+
59
## [0.13.5] - 2026-08-18
610

711
- Docs now list Domain Overview as available with a DataForSEO connection, and the v0.1.0 to v0.2.0 upgrade procedure moved to the legacy upgrades page.

components/research/ResearchSearchCard.test.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,13 @@ describe("ResearchSearchCard", () => {
209209
await waitFor(() => expect(onSubmit).toHaveBeenCalledWith(["rank tracker", "seo tool"]));
210210
expect(onSeedsChange).toHaveBeenLastCalledWith([]);
211211
});
212+
213+
it("wraps the compact desktop control row instead of overflowing the content column", () => {
214+
render(<ResearchSearchCard {...baseProps} />);
215+
216+
const seedInput = screen.getByRole("textbox", { name: "Seed keyword" });
217+
const controlRow = seedInput.closest('[class*="md:flex-row"]');
218+
expect(controlRow).not.toBeNull();
219+
expect(controlRow?.className).toMatch(/md:flex-wrap/);
220+
});
212221
});

components/research/ResearchSearchCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export function ResearchSearchCard({
173173
return (
174174
<Card className="p-4 sm:p-5" size="md">
175175
<form className="grid gap-3" onSubmit={handleSubmit(submit)}>
176-
<div className="flex flex-col gap-3 md:flex-row md:items-start">
176+
<div className="flex flex-col gap-3 md:flex-row md:flex-wrap md:items-start">
177177
<div className="flex min-h-[34px] flex-1 flex-wrap items-center gap-1.5 rounded-[9px] border border-border-strong bg-transparent px-2.5 py-0.5 focus-within:border-accent md:min-w-[240px]">
178178
{seeds.map((seed) => (
179179
<span

components/ui/DomainIconLayer.test.tsx

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { fireEvent, render, screen } from "@testing-library/react";
2-
import { describe, expect, it } from "vitest";
2+
import { act } from "react";
3+
import { hydrateRoot } from "react-dom/client";
4+
import { renderToString } from "react-dom/server";
5+
import { describe, expect, it, vi } from "vitest";
36
import { DomainIconLayer } from "./DomainIconLayer";
47

58
function loadProbe(image: HTMLElement, width: number, height: number) {
@@ -91,4 +94,64 @@ describe("DomainIconLayer", () => {
9194
expect(second).toBe(first);
9295
expect(second.className).toBe(first.className);
9396
});
97+
98+
it("paints the layer when a hydrated probe is already complete without a load event", async () => {
99+
const names = ["complete", "naturalWidth", "naturalHeight"] as const;
100+
const saved = names.map(
101+
(name) => [name, Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, name)] as const,
102+
);
103+
Object.defineProperty(HTMLImageElement.prototype, "complete", {
104+
configurable: true,
105+
get: () => true,
106+
});
107+
Object.defineProperty(HTMLImageElement.prototype, "naturalWidth", {
108+
configurable: true,
109+
get: () => 32,
110+
});
111+
Object.defineProperty(HTMLImageElement.prototype, "naturalHeight", {
112+
configurable: true,
113+
get: () => 32,
114+
});
115+
116+
const container = document.createElement("div");
117+
document.body.append(container);
118+
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
119+
let root: ReturnType<typeof hydrateRoot> | undefined;
120+
121+
try {
122+
container.innerHTML = renderToString(
123+
<DomainIconLayer src="https://icons.example.com/favicon.png" testId="domain-icon" />,
124+
);
125+
126+
expect(container.querySelector('[data-testid="domain-icon-probe"]')).not.toBeNull();
127+
expect(container.querySelector('[data-testid="domain-icon"]')).toBeNull();
128+
129+
root = hydrateRoot(
130+
container,
131+
<DomainIconLayer src="https://icons.example.com/favicon.png" testId="domain-icon" />,
132+
);
133+
134+
await act(async () => undefined);
135+
136+
expect(container.querySelector('[data-testid="domain-icon"]')).not.toBeNull();
137+
expect(container.querySelector('[data-testid="domain-icon"]')).toHaveStyle({
138+
backgroundImage: 'url("https://icons.example.com/favicon.png")',
139+
});
140+
expect(consoleError).not.toHaveBeenCalled();
141+
} finally {
142+
if (root) {
143+
const r = root;
144+
await act(async () => r.unmount());
145+
}
146+
container.remove();
147+
consoleError.mockRestore();
148+
for (const [name, desc] of saved) {
149+
if (desc) {
150+
Object.defineProperty(HTMLImageElement.prototype, name, desc);
151+
} else {
152+
delete (HTMLImageElement.prototype as unknown as Record<string, unknown>)[name];
153+
}
154+
}
155+
}
156+
});
94157
});

components/ui/DomainIconLayer.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useCallback, useRef, useState } from "react";
44

55
export type DomainIconLayerProps = {
66
size?: number;
@@ -10,13 +10,33 @@ export type DomainIconLayerProps = {
1010

1111
const MIN_ICON_DIMENSION = 32;
1212

13+
/**
14+
* Reports whether a probe image resolved to a square icon at least as large as
15+
* the minimum dimension. Shared by the ref recovery and the load handler.
16+
*/
17+
function isProbeValid(img: HTMLImageElement) {
18+
return (
19+
img.naturalWidth === img.naturalHeight &&
20+
img.naturalWidth >= MIN_ICON_DIMENSION &&
21+
img.naturalHeight >= MIN_ICON_DIMENSION
22+
);
23+
}
24+
1325
/**
1426
* Paints a verified domain icon as a background, leaving the caller's text fallback visible
1527
* when the icon service resolves to a smaller placeholder or an error.
1628
*/
1729
export function DomainIconLayer({ src, testId }: Readonly<DomainIconLayerProps>) {
1830
const [loadedSrc, setLoadedSrc] = useState<string | null>(null);
1931
const verifiedSrc = loadedSrc === src ? src : null;
32+
const srcRef = useRef(src);
33+
srcRef.current = src;
34+
35+
const probeRef = useCallback((img: HTMLImageElement | null) => {
36+
if (img?.complete && isProbeValid(img)) {
37+
setLoadedSrc(srcRef.current ?? null);
38+
}
39+
}, []);
2040

2141
if (!src) {
2242
return null;
@@ -36,17 +56,13 @@ export function DomainIconLayer({ src, testId }: Readonly<DomainIconLayerProps>)
3656
height={1}
3757
onError={() => setLoadedSrc((current) => (current === src ? null : current))}
3858
onLoad={(event) => {
39-
const { naturalHeight, naturalWidth } = event.currentTarget;
40-
if (
41-
naturalWidth === naturalHeight &&
42-
naturalWidth >= MIN_ICON_DIMENSION &&
43-
naturalHeight >= MIN_ICON_DIMENSION
44-
) {
59+
if (isProbeValid(event.currentTarget)) {
4560
setLoadedSrc(src);
4661
return;
4762
}
4863
setLoadedSrc((current) => (current === src ? null : current));
4964
}}
65+
ref={probeRef}
5066
src={src}
5167
width={1}
5268
/>

compose.worker.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ services:
1515
<<: *temporal-env
1616

1717
worker:
18-
image: ${BISIBILITY_WORKER_IMAGE:-docker.io/corgicorner/bisibility-worker:0.13.5}
18+
image: ${BISIBILITY_WORKER_IMAGE:-docker.io/corgicorner/bisibility-worker:0.13.6}
1919
pull_policy: ${BISIBILITY_PULL_POLICY:-missing}
2020
restart: unless-stopped
2121
environment:

compose.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ x-app-env: &app-env
9494

9595
services:
9696
db-migrations:
97-
image: ${BISIBILITY_IMAGE:-docker.io/corgicorner/bisibility:0.13.5}
97+
image: ${BISIBILITY_IMAGE:-docker.io/corgicorner/bisibility:0.13.6}
9898
pull_policy: ${BISIBILITY_PULL_POLICY:-missing}
9999
command: >
100100
sh -c 'npm run db:migrate
@@ -105,7 +105,7 @@ services:
105105
condition: service_healthy
106106

107107
app:
108-
image: ${BISIBILITY_IMAGE:-docker.io/corgicorner/bisibility:0.13.5}
108+
image: ${BISIBILITY_IMAGE:-docker.io/corgicorner/bisibility:0.13.6}
109109
pull_policy: ${BISIBILITY_PULL_POLICY:-missing}
110110
restart: unless-stopped
111111
ports:

distribution-manifest.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
{
22
"schemaVersion": 3,
3-
"release": "v0.13.5",
3+
"release": "v0.13.6",
44
"source": {
55
"repository": "https://github.com/CorgiCorner/bisibility",
6-
"tag": "v0.13.5"
6+
"tag": "v0.13.6"
77
},
88
"images": {
9-
"web": "ghcr.io/corgicorner/bisibility:0.13.5",
10-
"worker": "ghcr.io/corgicorner/bisibility-worker:0.13.5"
9+
"web": "ghcr.io/corgicorner/bisibility:0.13.6",
10+
"worker": "ghcr.io/corgicorner/bisibility-worker:0.13.6"
1111
},
1212
"artifacts": {
1313
"composeCompatibility": {
1414
"name": "docker-compose.self-host.yml",
15-
"sha256": "90df900c17962456218293da16278f59ef38f04e081cc1bdcafc90b0e51e7634"
15+
"sha256": "c0e44dacbda2133e4d97389ae2d448a0fc3dc531fe05da0fbf3d2c15e028cc0e"
1616
},
1717
"composeCore": {
1818
"name": "compose.yaml",
19-
"sha256": "e9c15f52a5dc13144555e61db6272e10d0e9c8f0842074777d643893e16bf2f7"
19+
"sha256": "18b77261f388311abb1a722ad17b09f6ca5b46711954fe21d839de93d426075a"
2020
},
2121
"composeTemporal": {
2222
"name": "compose.temporal.yaml",
2323
"sha256": "a8e9fd41bdbf41acd470c0032930a23b04d24a59f51b850364b1b1bcc7464715"
2424
},
2525
"composeWorker": {
2626
"name": "compose.worker.yaml",
27-
"sha256": "bde2e977617e23155366e8717c91750d3bc77c8ef7c5fcd479234219c2b7c936"
27+
"sha256": "e6066efff3903ad10fb5ab421948b244914d11a69f3498264354015e5a32c1db"
2828
},
2929
"environment": {
3030
"name": "bisibility.env.example",

docker-compose.self-host.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ x-app-env: &app-env
102102

103103
services:
104104
db-migrations:
105-
image: docker.io/corgicorner/bisibility:0.13.5
105+
image: docker.io/corgicorner/bisibility:0.13.6
106106
pull_policy: ${BISIBILITY_PULL_POLICY:-missing}
107107
command: >
108108
sh -c 'npm run db:migrate && if [ "$$DEMO_FIXED_OTP" = "1" ]; then node --experimental-strip-types prisma/seed.ts; fi'
@@ -112,7 +112,7 @@ services:
112112
condition: service_healthy
113113

114114
app:
115-
image: docker.io/corgicorner/bisibility:0.13.5
115+
image: docker.io/corgicorner/bisibility:0.13.6
116116
pull_policy: ${BISIBILITY_PULL_POLICY:-missing}
117117
restart: unless-stopped
118118
ports:
@@ -299,7 +299,7 @@ services:
299299
- "127.0.0.1:${TEMPORAL_UI_HOST_PORT:-8233}:8080"
300300

301301
worker:
302-
image: docker.io/corgicorner/bisibility-worker:0.13.5
302+
image: docker.io/corgicorner/bisibility-worker:0.13.6
303303
pull_policy: ${BISIBILITY_PULL_POLICY:-missing}
304304
profiles: [ "scheduled" ]
305305
restart: unless-stopped

docker-compose.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ x-app-env: &app-env
109109

110110
services:
111111
db-migrations:
112-
image: ${BISIBILITY_IMAGE:-docker.io/corgicorner/bisibility:0.13.5}
112+
image: ${BISIBILITY_IMAGE:-docker.io/corgicorner/bisibility:0.13.6}
113113
pull_policy: ${BISIBILITY_PULL_POLICY:-missing}
114114
build:
115115
<<: *app-build
@@ -123,7 +123,7 @@ services:
123123
condition: service_healthy
124124

125125
app:
126-
image: ${BISIBILITY_IMAGE:-docker.io/corgicorner/bisibility:0.13.5}
126+
image: ${BISIBILITY_IMAGE:-docker.io/corgicorner/bisibility:0.13.6}
127127
pull_policy: ${BISIBILITY_PULL_POLICY:-missing}
128128
build: *app-build
129129
restart: unless-stopped
@@ -311,7 +311,7 @@ services:
311311
- "127.0.0.1:${TEMPORAL_UI_HOST_PORT:-8233}:8080"
312312

313313
worker:
314-
image: ${BISIBILITY_WORKER_IMAGE:-docker.io/corgicorner/bisibility-worker:0.13.5}
314+
image: ${BISIBILITY_WORKER_IMAGE:-docker.io/corgicorner/bisibility-worker:0.13.6}
315315
pull_policy: ${BISIBILITY_PULL_POLICY:-missing}
316316
build:
317317
context: .

0 commit comments

Comments
 (0)