mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-15 00:58:44 +02:00
fix: handle missing/empty interest list gracefully for $hashtags
- Remove #t from filter when $hashtags resolves to empty array (prevents unusable queries) - Preserve other hashtags when $hashtags is empty - Add UI warning banners for: - Interest list not found (kind 10015) - Interest list exists but has no hashtags - Add interestListStatus computed state for UI feedback - Add tests for empty $hashtags resolution scenarios
This commit is contained in:
@@ -704,6 +704,16 @@ export default function ReqViewer({
|
||||
[interestListEvent],
|
||||
);
|
||||
|
||||
// Compute interest list status for UI feedback
|
||||
const interestListStatus = useMemo(() => {
|
||||
if (!needsInterestList) return null;
|
||||
if (!accountPubkey) return null; // Account required error handles this
|
||||
if (interestListEvent === undefined) return "loading";
|
||||
if (interestListEvent === null) return "not-found";
|
||||
if (hashtags.length === 0) return "empty";
|
||||
return "ok";
|
||||
}, [needsInterestList, accountPubkey, interestListEvent, hashtags.length]);
|
||||
|
||||
// Resolve $me, $contacts, and $hashtags aliases (memoized to prevent unnecessary object creation)
|
||||
const resolvedFilter = useMemo(
|
||||
() =>
|
||||
@@ -1285,6 +1295,28 @@ export default function ReqViewer({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Interest List Warning Banner */}
|
||||
{interestListStatus === "not-found" && (
|
||||
<div className="border-b border-border px-4 py-2 bg-warning/10 flex items-center gap-2">
|
||||
<Hash className="size-4 text-warning" />
|
||||
<span className="text-xs text-warning">
|
||||
No interest list found (kind 10015).{" "}
|
||||
<code className="bg-muted px-1 py-0.5 rounded">$hashtags</code>{" "}
|
||||
ignored.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{interestListStatus === "empty" && (
|
||||
<div className="border-b border-border px-4 py-2 bg-warning/10 flex items-center gap-2">
|
||||
<Hash className="size-4 text-warning" />
|
||||
<span className="text-xs text-warning">
|
||||
Interest list has no hashtags.{" "}
|
||||
<code className="bg-muted px-1 py-0.5 rounded">$hashtags</code>{" "}
|
||||
ignored.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Account Required Error */}
|
||||
{(needsAccount || needsInterestList) && !accountPubkey && (
|
||||
<div className="flex flex-col items-center justify-center h-full gap-4 p-8 text-center">
|
||||
|
||||
@@ -428,13 +428,24 @@ describe("resolveFilterAliases", () => {
|
||||
expect(result["#t"]).not.toContain("$hashtags");
|
||||
});
|
||||
|
||||
it("should handle $hashtags with empty hashtag list", () => {
|
||||
it("should remove #t from filter when $hashtags resolves to empty", () => {
|
||||
const filter: NostrFilter = { "#t": ["$hashtags"] };
|
||||
const result = resolveFilterAliases(filter, undefined, [], {
|
||||
hashtags: [],
|
||||
});
|
||||
|
||||
expect(result["#t"]).toEqual([]);
|
||||
// Empty #t should be removed from filter entirely
|
||||
expect(result["#t"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should preserve other hashtags when $hashtags resolves to empty", () => {
|
||||
const filter: NostrFilter = { "#t": ["$hashtags", "nostr", "bitcoin"] };
|
||||
const result = resolveFilterAliases(filter, undefined, [], {
|
||||
hashtags: [],
|
||||
});
|
||||
|
||||
// Other hashtags should be preserved
|
||||
expect(result["#t"]).toEqual(["nostr", "bitcoin"]);
|
||||
});
|
||||
|
||||
it("should preserve other hashtags when resolving $hashtags", () => {
|
||||
@@ -520,7 +531,8 @@ describe("resolveFilterAliases", () => {
|
||||
const result = resolveFilterAliases(filter, accountPubkey, []);
|
||||
|
||||
expect(result.authors).toEqual([accountPubkey]);
|
||||
expect(result["#t"]).toEqual([]); // Empty when no hashtags provided
|
||||
// Empty #t should be removed from filter entirely
|
||||
expect(result["#t"]).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -236,7 +236,14 @@ export function resolveFilterAliases(
|
||||
}
|
||||
|
||||
// Deduplicate
|
||||
resolved["#t"] = Array.from(new Set(resolvedTTags));
|
||||
const deduped = Array.from(new Set(resolvedTTags));
|
||||
|
||||
// If result is empty, remove #t from filter entirely to avoid unusable query
|
||||
if (deduped.length === 0) {
|
||||
delete resolved["#t"];
|
||||
} else {
|
||||
resolved["#t"] = deduped;
|
||||
}
|
||||
}
|
||||
|
||||
return resolved;
|
||||
|
||||
Reference in New Issue
Block a user