Skip to content

Sync fork main into upstream: Tap ingestion, advanced GraphQL queries, and frontend refresh#42

Open
Kzoeps wants to merge 151 commits intomainfrom
production
Open

Sync fork main into upstream: Tap ingestion, advanced GraphQL queries, and frontend refresh#42
Kzoeps wants to merge 151 commits intomainfrom
production

Conversation

@Kzoeps
Copy link
Copy Markdown

@Kzoeps Kzoeps commented Apr 21, 2026

Summary

This PR syncs the fork’s main branch into upstream main and brings over a large set of backend, frontend, infra, and DX improvements.
The biggest changes are:

  • replacing/augmenting ingestion with Tap sidecar support
  • adding much richer GraphQL querying capabilities
  • refreshing the frontend UI/branding and deployment setup

What’s included

GraphQL / query improvements

  • add per-collection filtering (where) support
  • add sorting support with sortBy / sortDirection
  • add backward pagination (last / before) and page size clamping
  • add opt-in totalCount on public connections
  • add cross-collection search
  • add did and rkey fields to typed record GraphQL types
  • add DB indexes and repository/query updates to support these features
  • add extensive unit and integration test coverage for filter/sort/search/pagination behavior

Tap sidecar integration

  • add internal/tap package with event parsing, consumer, admin client, and handler
  • wire Tap into the app with config/env support and feature flagging
  • add Tap health/status reporting
  • add follow-up fixes for auth, reconnection/backoff, validation, shutdown handling, and safety limits

Frontend / UX refresh

  • redesign the frontend with updated Hyperindex branding
  • add dark mode, theme provider, and theme toggle
  • refresh dashboard, docs, settings, onboarding, lexicons, and backfill pages
  • improve GraphiQL routing and API URL handling
  • make dashboard stats/activity queries public

Admin / product improvements

  • support batch lexicon registration from comma/newline-separated NSIDs
  • add admin DID batch picker UI
  • fix GraphQL NonNull handling for required fields with missing data

DevOps / CI / docs

  • add client Dockerfile and Railway deployment support
  • update CI to run on all PRs and include integration race coverage
  • expand environment variable and deployment docs
  • add project/agent workflow files used in the fork

- Add DIDFilterInput GraphQL type with only eq and in fields (no contains/startsWith/neq)
- Replace StringFilterInput with DIDFilterInput for the did field in WhereInput
- Introduce DIDFilter struct in repositories to carry eq and in conditions
- Update extractFilters to populate DIDFilter.EQ and DIDFilter.IN from GraphQL args
- Add buildDIDFilterClause helper to generate SQL WHERE conditions for DIDFilter
- Update all repository methods to accept DIDFilter instead of plain string
- Add tests for DIDFilterInput fields, extractFilters DID handling, and DID in filtering
Kzoeps and others added 23 commits April 15, 2026 14:50
feat: add batched admin DID picker with Bluesky typeahead
Deploy batch admin add and display
Merge pull request #13 from GainForest/main
…back

- Drop removeFromTap argument from purgeActor mutation and all call sites;
  Tap should not be touched by the indexer (separation of concerns, avoids
  partial-failure state where local data is deleted before Tap call fails)
- Remove RemoveRepoCallback type, field, setter, and startTap wiring
- Wrap DeleteByDID raw error with fmt.Errorf context
- Refactor TestActorsRepository_DeleteByDID to table-driven format

Co-Authored-By: Claude <noreply@anthropic.com>
…d add normalization

- Rename NEXT_PUBLIC_API_URL → NEXT_PUBLIC_HYPERINDEX_URL across all files
- Add NEXT_PUBLIC_HYPERINDEX_URL to env.ts with normalizePublicURL()
- Update HYPERINDEX_URL fallback chain: HYPERINDEX_URL → NEXT_PUBLIC_HYPERINDEX_URL → http://127.0.0.1:8080
- Drop HYPERGOAT_URL entirely from env.ts and graphql/client.ts
- Import env.HYPERINDEX_URL in graphql/client.ts instead of reading process.env directly
- Add inline normalizeUrl helper to next.config.ts (can't import from src/)
- Update Dockerfile ARG/ENV, .env.example comments, and docs/ENV_VARS.md

Co-Authored-By: Claude <noreply@anthropic.com>
Redirect target was resolving to the internal Railway address (0.0.0.0:8080)
instead of the public domain because request.url reflects the internal proxy
address. Fall back to requestUrl.origin for local dev.

Co-Authored-By: Claude <noreply@anthropic.com>
Prevents the GraphiQL endpoint URL from being treated as a relative path
by the browser when EXTERNAL_BASE_URL is set without a protocol prefix,
which caused the hostname to be doubled in the request path.

Co-Authored-By: Claude <noreply@anthropic.com>
Whitespace env values were collapsing to "" inside normalizeUrl/normalizePublicURL,
causing the localhost fallback to never be reached. Apply the fallback via ||
after normalization so blank/whitespace values correctly fall through.

Co-Authored-By: Claude <noreply@anthropic.com>
…alBaseURL

Co-Authored-By: Claude <noreply@anthropic.com>
feat: add actor purge workflow and identity-based cleanup
fix: harden admin purge and env validation
chore: stop tracking local Beads runtime files
fix: gate settings UI to configured admin DIDs
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperindex Ready Ready Preview, Comment Apr 21, 2026 11:58am

Request Review

@railway-app
Copy link
Copy Markdown

railway-app bot commented Apr 21, 2026

🚅 Environment hyperindex-pr-42 in hypercerts has no services deployed.

4 services not affected by this PR
  • PDS entryway
  • sds-eu-west4
  • pds-eu-west4
  • pds1

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 21, 2026

Important

Review skipped

Too many files!

This PR contains 151 files, which is 1 over the limit of 150.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0c6ad967-3392-4a5a-98fb-2c33f1810fe4

📥 Commits

Reviewing files that changed from the base of the PR and between 9de3ef9 and c9f6450.

⛔ Files ignored due to path filters (1)
  • client/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (151)
  • .agents/skills/deploy-railway/SKILL.md
  • .beads/.gitignore
  • .beads/README.md
  • .beads/config.yaml
  • .beads/interactions.jsonl
  • .beads/issues.jsonl
  • .beads/metadata.json
  • .coderabbit.yaml
  • .env.example
  • .githooks/pre-commit
  • .github/workflows/ci.yml
  • .gitignore
  • Makefile
  • README.md
  • client/.env.example
  • client/Dockerfile
  • client/components.json
  • client/next.config.ts
  • client/package.json
  • client/src/app/api/admin/graphql/route.ts
  • client/src/app/api/graphql/route.ts
  • client/src/app/api/oauth/callback/route.ts
  • client/src/app/api/oauth/client-metadata.json/route.ts
  • client/src/app/backfill/page.tsx
  • client/src/app/docs/agents/route.ts
  • client/src/app/docs/page.tsx
  • client/src/app/globals.css
  • client/src/app/graphiql/route.ts
  • client/src/app/layout.tsx
  • client/src/app/lexicons/page.tsx
  • client/src/app/onboarding/page.tsx
  • client/src/app/page.tsx
  • client/src/app/settings/page.tsx
  • client/src/components/ThemeProvider.tsx
  • client/src/components/ThemeToggle.tsx
  • client/src/components/admin/AdminDidBatchPicker.tsx
  • client/src/components/dashboard/ActivityChart.tsx
  • client/src/components/dashboard/RecentActivity.tsx
  • client/src/components/dashboard/StatsCards.tsx
  • client/src/components/layout/GeometricBackground.tsx
  • client/src/components/layout/Header.tsx
  • client/src/components/ui/Alert.tsx
  • client/src/components/ui/Button.tsx
  • client/src/components/ui/Card.tsx
  • client/src/components/ui/Input.tsx
  • client/src/components/ui/badge.tsx
  • client/src/components/ui/command.tsx
  • client/src/lib/auth/client.ts
  • client/src/lib/env.test.ts
  • client/src/lib/env.ts
  • client/src/lib/graphql/client.ts
  • client/src/lib/graphql/mutations.ts
  • client/src/lib/graphql/queries.ts
  • client/src/lib/use-debounce.ts
  • client/src/types/index.ts
  • cmd/hypergoat/main.go
  • cmd/hypergoat/main_test.go
  • docker-compose.postgres.yml
  • docker-compose.tap.yml
  • docker-compose.yml
  • go.mod
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/database/migrations/postgres/006_add_composite_index.down.sql
  • internal/database/migrations/postgres/006_add_composite_index.up.sql
  • internal/database/migrations/sqlite/006_add_composite_index.down.sql
  • internal/database/migrations/sqlite/006_add_composite_index.up.sql
  • internal/database/repositories/actors.go
  • internal/database/repositories/actors_test.go
  • internal/database/repositories/records.go
  • internal/database/repositories/records_filter_test.go
  • internal/database/repositories/records_test.go
  • internal/graphql/admin/handler_test.go
  • internal/graphql/admin/resolvers.go
  • internal/graphql/admin/resolvers_purge_test.go
  • internal/graphql/admin/schema.go
  • internal/graphql/admin/types.go
  • internal/graphql/query/connection.go
  • internal/graphql/query/connection_test.go
  • internal/graphql/schema/builder.go
  • internal/graphql/schema/builder_test.go
  • internal/graphql/types/filters.go
  • internal/graphql/types/filters_test.go
  • internal/graphql/types/mapper.go
  • internal/graphql/types/object.go
  • internal/graphql/types/types_test.go
  • internal/integration/doc.go
  • internal/integration/graphql_filter_test.go
  • internal/integration/integration_test.go
  • internal/lexicon/types.go
  • internal/lexicon/types_test.go
  • internal/oauth/bridge.go
  • internal/oauth/dpop.go
  • internal/oauth/dpop_test.go
  • internal/oauth/middleware_test.go
  • internal/server/graphiql.go
  • internal/server/handlers_test.go
  • internal/server/oauth_handlers.go
  • internal/server/oauth_handlers_test.go
  • internal/tap/admin.go
  • internal/tap/admin_test.go
  • internal/tap/consumer.go
  • internal/tap/consumer_test.go
  • internal/tap/event.go
  • internal/tap/event_test.go
  • internal/tap/handler.go
  • internal/tap/handler_test.go
  • internal/tap/identity_policy_test.go
  • railway.toml
  • testdata/lexicons/app/bsky/richtext/facet.json
  • testdata/lexicons/app/certified/actor/organization.json
  • testdata/lexicons/app/certified/actor/profile.json
  • testdata/lexicons/app/certified/badge/award.json
  • testdata/lexicons/app/certified/badge/definition.json
  • testdata/lexicons/app/certified/badge/response.json
  • testdata/lexicons/app/certified/defs.json
  • testdata/lexicons/app/certified/link/evm.json
  • testdata/lexicons/app/certified/location.json
  • testdata/lexicons/org/hyperboards/board.json
  • testdata/lexicons/org/hyperboards/displayProfile.json
  • testdata/lexicons/org/hypercerts/claim/activity.json
  • testdata/lexicons/org/hypercerts/claim/contribution.json
  • testdata/lexicons/org/hypercerts/claim/contributorInformation.json
  • testdata/lexicons/org/hypercerts/claim/rights.json
  • testdata/lexicons/org/hypercerts/collection.json
  • testdata/lexicons/org/hypercerts/context/acknowledgement.json
  • testdata/lexicons/org/hypercerts/context/attachment.json
  • testdata/lexicons/org/hypercerts/context/evaluation.json
  • testdata/lexicons/org/hypercerts/context/measurement.json
  • testdata/lexicons/org/hypercerts/defs.json
  • testdata/lexicons/org/hypercerts/funding/receipt.json
  • testdata/lexicons/org/hypercerts/helper/workScopeTag.json
  • testdata/lexicons/org/hypercerts/workscope/cel.json
  • testdata/lexicons/org/hypercerts/workscope/tag.json
  • testdata/lexicons/pub/leaflet/blocks/blockquote.json
  • testdata/lexicons/pub/leaflet/blocks/bskyPost.json
  • testdata/lexicons/pub/leaflet/blocks/button.json
  • testdata/lexicons/pub/leaflet/blocks/code.json
  • testdata/lexicons/pub/leaflet/blocks/header.json
  • testdata/lexicons/pub/leaflet/blocks/horizontalRule.json
  • testdata/lexicons/pub/leaflet/blocks/iframe.json
  • testdata/lexicons/pub/leaflet/blocks/image.json
  • testdata/lexicons/pub/leaflet/blocks/math.json
  • testdata/lexicons/pub/leaflet/blocks/orderedList.json
  • testdata/lexicons/pub/leaflet/blocks/page.json
  • testdata/lexicons/pub/leaflet/blocks/poll.json
  • testdata/lexicons/pub/leaflet/blocks/text.json
  • testdata/lexicons/pub/leaflet/blocks/unorderedList.json
  • testdata/lexicons/pub/leaflet/blocks/website.json
  • testdata/lexicons/pub/leaflet/pages/linearDocument.json
  • testdata/lexicons/pub/leaflet/richtext/facet.json

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch production

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment on lines +101 to +107
| `NEXT_PUBLIC_API_URL` | `https://api.hi.gainforest.app` |
| `HYPERINDEX_URL` | `https://api.hi.gainforest.app` |
| `COOKIE_SECRET` | *(set on Railway, do not change)* |
| `ATPROTO_JWK_PRIVATE` | *(ES256 JWK, set on Railway, do not change)* |

**Note:** `NEXT_PUBLIC_API_URL` is a build-time variable (inlined by Next.js during `npm run build`). The `client/Dockerfile` declares `ARG NEXT_PUBLIC_API_URL` so Railway passes it during Docker build.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 SKILL.md references the stale env var NEXT_PUBLIC_API_URL at lines 101 and 106, but this PR renames it to NEXT_PUBLIC_HYPERINDEX_URL everywhere else (Dockerfile, env.ts, .env.example, docs/ENV_VARS.md). A developer following SKILL.md to deploy on Railway will set the wrong build arg, causing Next.js to silently fall back to http://127.0.0.1:8080, which breaks the production frontend.

Extended reasoning...

What the bug is and how it manifests

The newly-added .agents/skills/deploy-railway/SKILL.md file was created by this PR. The Frontend env vars table on line 101 lists NEXT_PUBLIC_API_URL as the build-time variable to set. The note on line 106 states "The client/Dockerfile declares ARG NEXT_PUBLIC_API_URL". Both of these are wrong: this same PR renames the variable from NEXT_PUBLIC_API_URL to NEXT_PUBLIC_HYPERINDEX_URL across the entire codebase.

The specific code path that triggers it

client/Dockerfile (added in this PR) declares:

ARG NEXT_PUBLIC_HYPERINDEX_URL
ENV NEXT_PUBLIC_HYPERINDEX_URL=$NEXT_PUBLIC_HYPERINDEX_URL

Next.js bakes NEXT_PUBLIC_* variables into the JavaScript bundle at build time. If Railway sets NEXT_PUBLIC_API_URL (as SKILL.md instructs), Docker never sees NEXT_PUBLIC_HYPERINDEX_URL, so it remains unset. The client-side code in client/src/lib/env.ts then resolves NEXT_PUBLIC_HYPERINDEX_URL to an empty string and falls back to http://127.0.0.1:8080 — a loopback address unreachable from the browser in production.

Why existing code doesn't prevent it

client/src/lib/env.ts validates the URL configuration at module load time, but only checks that HYPERINDEX_URL (the server-side var) doesn't point to the client origin — it does not warn when NEXT_PUBLIC_HYPERINDEX_URL is missing. The misconfiguration produces no visible error; the frontend simply makes all GraphQL requests to 127.0.0.1:8080 and gets network failures in the user's browser.

What the impact would be

Any deployer who follows the SKILL.md instructions will end up with a broken Railway frontend. Every GraphQL query from the browser fails silently (or with a CORS/network error). The Vercel-style deployment works because Vercel injects env vars differently, but the Railway Docker build path is exactly what SKILL.md is trying to guide.

How to fix it

In SKILL.md, replace both occurrences of NEXT_PUBLIC_API_URL with NEXT_PUBLIC_HYPERINDEX_URL. Also, docs/ENV_VARS.md (added in the same PR) explicitly documents NEXT_PUBLIC_API_URL as "Removed — Renamed to NEXT_PUBLIC_HYPERINDEX_URL", so the skill file is already inconsistent with the authoritative reference within this same PR.

Step-by-step proof

  1. Developer reads SKILL.md and sets Railway build var: NEXT_PUBLIC_API_URL=https://api.hi.gainforest.app
  2. Railway runs: docker build --build-arg NEXT_PUBLIC_API_URL=... client/
  3. client/Dockerfile declares ARG NEXT_PUBLIC_HYPERINDEX_URL — the passed NEXT_PUBLIC_API_URL build arg is unknown and silently ignored.
  4. NEXT_PUBLIC_HYPERINDEX_URL remains empty inside the Docker build context.
  5. npm run build runs; Next.js inlines process.env.NEXT_PUBLIC_HYPERINDEX_URL as "" into the JS bundle.
  6. At runtime, env.ts resolves: normalizePublicURL("") → "", then falls back to http://127.0.0.1:8080.
  7. All browser-side GraphQL calls go to http://127.0.0.1:8080/graphql — a loopback address the user's browser cannot reach — silently breaking the production frontend.

Comment on lines +30 to +37
// MaxINListSize is the maximum number of values allowed in an IN filter clause.
// SQLite has a hard 999 parameter limit (SQLITE_MAX_VARIABLE_NUMBER).
// We cap well below that to leave room for other query parameters.
MaxINListSize = 100

// MaxFilterConditions is the maximum number of individual filter conditions allowed per query.
// The DID filter does not count toward this cap.
MaxFilterConditions = 20
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The per-filter MaxINListSize=100 cap does not protect against the aggregate parameter count when multiple IN filters are combined. With MaxFilterConditions=20, a worst-case query generates 20×100 + 4 fixed params = 2004 SQL parameters — more than double SQLite's hard SQLITE_MAX_VARIABLE_NUMBER=999 limit stated in the code comments. Even a modest query with 10 fields each filtered by 100 IN values (1004 params) exceeds this limit. Neither buildFilterClause nor GetByCollectionSortedWithKeysetCursor checks the aggregate parameter count across all filter clauses combined, and the code comment actively misleads maintainers by implying the 100-value cap is sufficient protection.

Extended reasoning...

What the bug is and how it manifests

The code introduces two new constants: MaxINListSize = 100 (records.go:31-32) and MaxFilterConditions = 20 (records.go:35-37). The comment on MaxINListSize reads: "SQLite has a hard 999 parameter limit (SQLITE_MAX_VARIABLE_NUMBER). We cap well below that to leave room for other query parameters." This reasoning is only valid when there is a single IN clause in the query. The comment was designed with a single filter in mind, not the multiplicative case of 20 simultaneous IN filters.

The specific code path that triggers it

A user constructs a valid typed collection query with multiple in filter conditions, e.g. 10 different fields each with 100 values: {field1: {in: [...100 values...]}, field2: {in: [...100 values...]}, ...}. In buildFilterClause (records.go:381-473), each in operator adds len(inVals) placeholders. The individual per-filter enforcement at records.go:461-462 rejects any single IN list over 100, but there is no check on the cumulative parameter count. The query is then passed to SQLite with 10×100 + 1 (collection) + 3 (keyset cursor) = 1004 bound parameters.

Why existing code doesn't prevent it

Validation in builder.go:607-609 enforces MaxFilterConditions=20 per query — this limits the number of individual filter conditions but not the aggregate number of SQL parameters they consume. An IN filter with 100 values counts as 1 condition but 100 parameters. There is no code anywhere in buildFilterClause, GetByCollectionSortedWithKeysetCursor, GetByCollectionFilteredWithKeysetCursor, or the GraphQL resolver layer that computes or caps the total SQL parameter count across all combined filters.

What the impact would be

On SQLite builds where SQLITE_MAX_VARIABLE_NUMBER=999 (the pre-3.32.0 default), any query combining 10 or more in filters with 100 values each will cause SQLite to return a "too many SQL variables" error, which surfaces as a GraphQL error. One verifier notes that modernc.org/sqlite v1.44.3 likely bundles SQLite 3.32.0+ (which raised the limit to 32766), so the error may not manifest in production today. However, the code comment actively states the 999 limit as a design constraint, the invariant is violated by the constants introduced in this PR, and any deployment using a system SQLite or a build with the old limit would fail silently.

Step-by-step proof

  1. Schema has a collection with 10+ string fields (e.g. org.hypercerts.collection is a realistic example).
  2. A client sends: orgHypercertsCollection(where: {field1: {in: [v1...v100]}, field2: {in: [v1...v100]}, ..., field10: {in: [v1...v100]}})
  3. extractFilters (builder.go:571) maps each field to 10 FieldFilter structs with operator in and 100-element Value slices. 10 conditions < MaxFilterConditions=20, so no error here.
  4. buildFilterClause (records.go:381) iterates and generates 10 × 100 = 1000 ?/$N placeholders.
  5. GetByCollectionSortedWithKeysetCursor prepends placeholder 1 (collection) and placeholders 2-4 (keyset cursor) = 4 fixed params.
  6. Total SQL parameters = 1004 > 999. SQLite (legacy builds) returns SQLITE_ERROR: too many SQL variables.

How to fix it

Track the total running parameter count in buildFilterClause (or in the callers), and return an error when the cumulative count would exceed a safe threshold (e.g. 950, leaving headroom for the fixed params). Alternatively, reduce MaxINListSize to a value where even the worst-case combination of MaxFilterConditions IN filters stays under the limit: 950 / 20 = 47 values per IN list would be safe. The existing comment should also be updated to reflect the multiplicative interaction between the two constants.

Comment on lines +875 to +878
searchQuery, _ := p.Args["query"].(string)
if utf8.RuneCountInString(searchQuery) < 3 {
return nil, fmt.Errorf("search query must be at least 3 characters")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The API documentation states the search query minimum is 2 characters, but the code in createSearchResolver() enforces a minimum of 3 characters. Clients following the documented contract will receive unexpected errors for 2-character queries; fix by updating the docs to say "minimum 3 characters".

Extended reasoning...

Bug: Doc/code mismatch on search query minimum length

The search resolver in internal/graphql/schema/builder.go (lines 875-878) enforces a minimum of 3 Unicode rune characters via utf8.RuneCountInString(searchQuery) < 3, returning the error "search query must be at least 3 characters". However, the agent API documentation table in client/src/app/docs/agents/route.ts (line 512) describes the query argument as "Search text (minimum 2 characters)".

Code path that triggers it: Any GraphQL client calling the search field with a 2-character query (e.g., {search(query: "hi") {...}}) will be rejected by the resolver, even though the published documentation explicitly allows it.

Why existing code doesn't prevent it: There is no integration test or documentation linting step that cross-checks the prose description against the runtime validation threshold. The code comment in builder.go even says "minimum 3 runes", but this was never propagated back to the documentation string.

Impact: Any API consumer who reads the documentation and builds logic around a 2-character minimum will encounter confusing, undocumented errors in production. This is a broken API contract that violates the principle of least surprise, particularly for callers building search-as-you-type UIs that start querying at 2 characters.

How to fix: Update the documentation table row in client/src/app/docs/agents/route.ts line 512 from Search text (minimum 2 characters) to Search text (minimum 3 characters) to match the enforced runtime behavior. Alternatively, lower the code threshold to 2 if 2-character searches are intended to be supported.

Step-by-step proof:

  1. A client reads the docs and sees: | query | String! | Yes | Search text (minimum 2 characters) |
  2. Client sends GraphQL query: { search(query: "hi") { edges { node { uri } } } }
  3. The resolver runs utf8.RuneCountInString("hi") < 32 < 3true
  4. Resolver returns error: "search query must be at least 3 characters"
  5. Client receives a 200 with a GraphQL error body, contradicting the documented minimum of 2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants