Finalize the Effect View rename and refresh docs, examples, and tooling (#59)
Publish / publish (push) Successful in 3m23s
Lint / lint (push) Successful in 48s

## Summary

- Rename `effect-fc-next` to `effect-view` and update package metadata, internal symbols, imports, and repository references.
- Promote the Effect 4 example to `packages/example` and move the legacy Effect 3 example to `packages/effect-fc-example`.
- Add `effect-view` to the npm publishing workflow.
- Update the Vite Fast Refresh plugin for `effect-view` and rename `effectViewPlugin()` to `effectView()`.
- Upgrade Effect, React, TypeScript, build tooling, and related dependencies.
- Refresh the documentation and homepage.

## Documentation

- Add a dedicated Async guide covering:
  - Suspense fallbacks
  - Hook ordering
  - Memoization
  - Structural prop equality
- Clarify synchronous and asynchronous component behavior.
- Document runner scope and service requirements.
- Explain stable construction of Query, Mutation, and Form instances.
- Expand focused Lens and Form examples, including curried focus helpers.
- Document the optional `@effect/platform-browser` dependency for window-focus query refresh.
- Improve the Forms guide structure and examples.
- Fix responsive homepage headline wrapping and update homepage copy.

## Notable API changes

- Package rename:

  ```diff
  - effect-fc-next
  + effect-view

---------

Co-authored-by: Julien Valverdé <julien.valverde@mailo.com>
Reviewed-on: #59
This commit was merged in pull request #59.
This commit is contained in:
2026-07-27 04:11:42 +02:00
parent ec21d49dce
commit 4b712de788
111 changed files with 1536 additions and 1278 deletions
+3 -3
View File
@@ -47,8 +47,8 @@ const refreshIdentifier = "__effectViewRefresh"
const acceptIdentifier = "__effectViewAccept"
const isSupportedPackage = (source: string): boolean =>
source === "effect-fc-next"
|| source === "effect-fc-next/Component"
source === "effect-view"
|| source === "effect-view/Component"
const collectImports = (
sourceFile: ts.SourceFile,
@@ -342,7 +342,7 @@ const scriptKind = (id: string): ts.ScriptKind => id.endsWith(".tsx")
*
* Place this plugin before `@vitejs/plugin-react`.
*/
export function effectViewPlugin(
export function effectView(
options: EffectViewRefreshOptions = {},
): Plugin {
const include = options.include ?? defaultInclude
+9 -9
View File
@@ -1,7 +1,7 @@
import path from "node:path"
import type { Plugin } from "vite"
import { describe, expect, it } from "vitest"
import { effectViewPlugin } from "./index.js"
import { effectView } from "./index.js"
type TransformHook = Extract<
@@ -31,12 +31,12 @@ const runTransform = async (
const transform = (
code: string,
id = path.join(process.cwd(), "src/View.tsx"),
): Promise<string | undefined> => runTransform(effectViewPlugin(), code, id)
): Promise<string | undefined> => runTransform(effectView(), code, id)
describe("effectViewPlugin", () => {
describe("effectView", () => {
it("wraps a const Effect View descriptor", async () => {
const result = await transform(`
import { Component } from "effect-fc-next"
import { Component } from "effect-view"
import * as React from "react"
export const CounterView = Component.make("CounterView")(function*() {
@@ -53,7 +53,7 @@ export const CounterView = Component.make("CounterView")(function*() {
it("registers a pipeline before withContext", async () => {
const result = await transform(`
import { Component } from "effect-fc-next"
import { Component } from "effect-view"
const Page = Component.make("Page")(function*() {
return <main />
@@ -69,7 +69,7 @@ const Page = Component.make("Page")(function*() {
it("wraps a class's complete trait pipeline", async () => {
const result = await transform(`
import { Async, Component, Memoized } from "effect-fc-next"
import { Async, Component, Memoized } from "effect-view"
class PostView extends Component.make("PostView")(function*() {
const value = yield* Component.useOnMount(load)
@@ -88,7 +88,7 @@ class PostView extends Component.make("PostView")(function*() {
it("supports aliased Component imports and refresh reset", async () => {
const result = await transform(`
// @refresh reset
import { Component as View } from "effect-fc-next"
import { Component as View } from "effect-view"
const Probe = View.makeUntraced(function*() {
return null
@@ -109,11 +109,11 @@ export function View() {
})
it("keeps instrumenting a module when all Effect Views are removed", async () => {
const plugin = effectViewPlugin() as Plugin
const plugin = effectView() as Plugin
const id = path.join(process.cwd(), "src/Removed.tsx")
await runTransform(plugin, `
import { Component } from "effect-fc-next"
import { Component } from "effect-view"
export const Probe = Component.makeUntraced(function*() {
return null
})
+2 -2
View File
@@ -1,5 +1,5 @@
import type * as Component from "effect-fc-next/Component"
import * as Refreshable from "effect-fc-next/Refreshable"
import type * as Component from "effect-view/Component"
import * as Refreshable from "effect-view/Refreshable"
import { describe, expect, it, vi } from "vitest"
import { accept, register } from "./runtime.js"
+2 -2
View File
@@ -1,5 +1,5 @@
import type * as Component from "effect-fc-next/Component"
import * as Refreshable from "effect-fc-next/Refreshable"
import type * as Component from "effect-view/Component"
import * as Refreshable from "effect-view/Refreshable"
export interface HotContext {
readonly data: Record<string, unknown>