feat(blog): phase-2 core-primitives adoption sweep#142
Open
olliethedev wants to merge 10 commits into
Open
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
…alization fallback
Every user-visible string resolves as localization?.KEY ?? t("blog.<area>.<name>", "English default"):
an app-provided overrides.localization still wins byte-for-byte, otherwise the string goes through
the i18n provider (or its literal English default without one). Client resolver schemas now carry
translatable required-field messages; server-side schemas.ts is unchanged. Adds the i18n key
reference docs page with the blog key catalog.
Co-authored-by: Cursor <cursoragent@cursor.com>
…ete control in CanAccess Routes declare ComposedRoute permission (blog:draft/read, blog:post/create, blog:post/update) and the edit form's delete button/dialog is wrapped in <CanAccess resource="blog:post" action="delete">. Without an auth provider everything renders exactly as before. Co-authored-by: Cursor <cursoragent@cursor.com>
Replaces the search modal's useState query with useListState("blog-posts", { q }) —
?q=... while searching (history: replace), clean URL when empty.
Co-authored-by: Cursor <cursoragent@cursor.com>
Blog's local useDebounce copy now re-exports the shared hook from @btst/stack/plugins/client/hooks (useThrottle stays plugin-local). Registry JSON regenerated for the updated component sources. Co-authored-by: Cursor <cursoragent@cursor.com>
…-trip, and i18n precedence Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
✅ Shadcn registry validated — no registry changes detected. |
…ynced search Typing in the blog search modal syncs ?q= to the URL via useListState. Two framework-level issues surfaced in codegen E2E: - TanStack/React Router page factories resolved the route on every render, producing a new PageComponent identity per search-param navigation and remounting the page subtree (closing the modal). Memoize getRoute() like RouteRenderer already does. - SearchModal's effect was keyed on the externalResults identity, so each async result set re-invoked searchFn -> setListState -> setSearchParams, restarting the navigation and looping forever on React Router/Next.js. Split result syncing into its own effect. Co-authored-by: Cursor <cursoragent@cursor.com>
… query length bound - useListState degrades to plain local state when the router has no getSearchParams/setSearchParams bindings, instead of silently dropping updates (search kept working via URL only with a full binding before). - SearchModal seeds its input from the persisted query in the open event and only notifies searchFn once the debounce settles on a value the parent has not seen, so opening the dialog no longer clears ?q=. - PostListQuerySchema bounds query to 200 chars. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 85e79ae. Configure here.
… clear The UI Builder page embeds its own sonner Toaster next to the app-root one, so a toast can resolve to two elements. Now that page subtrees are no longer remounted on every render, both copies stay visible until dismissal, tripping strict mode. All copies share one store and dismiss together, so waiting on the first is equivalent. Co-authored-by: Cursor <cursoragent@cursor.com>
…uter binding Local fallback state is now a patch overlaid on the parsed URL state instead of an all-or-nothing snapshot, so routers exposing only getSearchParams keep re-reading the URL (back/forward, external changes) while writes land in the overlay instead of being dropped. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Part of #136 — completes the blog reference sweep (follows #141's
createResource/useFormmigration). Blog is the reference implementation the seven remaining plugin sweeps should copy.#136 checklist mapping
sonnerimports withuseNotify()feat(blog): replace direct sonner toasts with useNotify in image fieldimage-field.tsxwas the only remaining importer; also drops an accidental duplicatetoast.errorin the upload catcht("blog.<area>.<name>", "Default")feat(blog): convert UI strings to useTranslate with override-wins localization fallback<CanAccess>feat(blog): gate drafts/new/edit routes with permissions and wrap delete control in CanAccessComposedRoutepermissionpropuseListStatefeat(blog): sync post search query to the URL via useListStateuseState; namespace"blog-posts",?q=withhistory: replace, clean URL when emptyrefactor(blog): reuse core useDebounce and regenerate blog registryuseDebouncere-exports the core hook;BLOG_LOCALIZATIONdefault-merge plumbing removed from all components (the constant stays as theBlogLocalizationtype source and default catalog)test(blog): cover notify, CanAccess delete gating, useListState round-trip, and i18n precedencesrc/plugins/blog/__tests__/client-sweep.test.tsxConventions established (for the remaining sweeps)
localization?.CONSTANT ?? t("plugin.area.name", "English default")— an app-providedoverrides.localizationwins byte-for-byte (public API unchanged), otherwiset()with a string-literal default soscripts/extract-i18n-keys.tskeeps working. Key format mirrors the constant:BLOG_FORMS_TITLE_LABEL→blog.forms.titleLabel. Params use core{{param}}interpolation. New docs page:docs/content/docs/i18n.mdx(key catalog, regenerable via the extract script).<plugin>:<entity>with CRUD verbs —blog:draft/read(drafts route),blog:post/create(new),blog:post/update(edit),blog:post/delete(delete control, withparams={{ id }}). Route gating viaComposedRoute'spermissionprop; in-page controls via<CanAccess>.useListState("<plugin>-<list>", ...)(here"blog-posts"), defaults omitted from the URL,history: "replace"for type-ahead state.Non-negotiables
overrides.localizationbehaves as before).t()returns English defaults,<CanAccess>/permissionare no-ops without an auth provider,useNotify()falls back to sonner,useListStateis inert without router search-param bindings.Proof
Out of scope (noted for phase 3): meta/SEO generator strings in
plugin.tsx(not hook contexts), AI-context registration strings, and thelocalizationoverride field type itself.Core fixes surfaced by the sweep (E2E)
The URL-synced search exposed two pre-existing framework-glue bugs, fixed in
fix(core): stabilize route identity and search-modal effect for URL-synced search:createTanStackPageOptions/createReactRouterPageresolved the route on every render, so a search-param navigation produced a newPageComponentidentity and remounted the whole page subtree (closing the search modal on TanStack).getRoute()is now memoized, matching whatRouteRendereralready did.SearchModal's effect was keyed on theexternalResultsarray identity, so every async result set re-invokedsearchFn→setListState→setSearchParams, restarting the router navigation and looping forever on React Router/Next.js (E2E timeout). Result syncing is now a separate effect.Verified locally:
smoke.blog.spec.ts(18 tests) passes on all three codegen frameworks (Next.js, TanStack, React Router).Test plan
pnpm build,pnpm typecheck,pnpm lint,pnpm test— all green (362 tests, incl. 10 new)knip --strictclean; registry JSON regenerated viabuild-registryMade with Cursor