Awesome-omni-skill write-data-type-ref
Write a reference documentation page for a specific data type in ZIO Blocks. Use when the user asks to document a data type, write an API reference for a type, or create a reference page for a class/trait/object.
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data-ai/write-data-type-ref" ~/.claude/skills/diegosouzapw-awesome-omni-skill-write-data-type-ref && rm -rf "$T"
skills/data-ai/write-data-type-ref/SKILL.mdWrite Data Type Reference Page
Write a comprehensive reference documentation page for a ZIO Blocks data type.
Target Type
$ARGUMENTS
Step 1: Deep Source Code Research
Before writing anything, build a complete mental model of the type:
-
Find the source file: Use Glob to find the primary source file for the type. Check all Scala version directories (
). The type may have platform-specific or version-specific variants.*/src/main/scala*/ -
Read the full source: Read the entire source file. Identify:
- The type signature (class/trait/object, type parameters, variance, extends clauses)
- All public methods, their signatures, and what they do
- Companion object methods (factory methods, smart constructors, predefined instances)
- Nested types and type aliases
- Implicit instances and extension methods
-
Find tests: Search
for test files referencing the type. Tests reveal:*/src/test/scala/- Intended usage patterns and idioms
- Edge cases and expected behavior
- Real-world examples
-
Find existing examples: Use Glob and Grep to locate examples in
or any directory with "examples" in its name.schema-examples/ -
Find usages: Grep for the type name across the codebase to find how it's used by other modules — this reveals integration points and relationships.
-
Read related docs: Check
anddocs/
for pages that reference this type.docs/reference/ -
Search GitHub history: Run
to search GitHub issues, PRs, and comments for discussions about the type. Use the results to:sbt "gh-query --verbose <TypeName>"- Understand design decisions and rationale behind the API
- Find known caveats, gotchas, or non-obvious behavior surfaced in issues
- Discover common user questions or pain points to address in the docs
- Identify changelog entries or breaking changes worth noting
- Surface examples or idioms shared by contributors in PRs
Run multiple queries as needed (e.g., the simple type name, the fully-qualified name, related feature keywords) to get thorough coverage.
Step 2: Write the Documentation
File Location and Frontmatter
Place the file in
docs/reference/<type-name-kebab-case>.md:
--- id: <kebab-case-id> title: "<TypeName>" ---
The
id must match the filename (without .md).
Document Structure
Follow this structure precisely. Every section below marked (required) must appear. Sections marked (if applicable) should only appear when relevant.
1. Opening Definition (required)
Start with a concise, technical definition. Use inline code for the type signature. Explain the type parameters. State the core purpose in 1-3 sentences.
Pattern:
`TypeName[A]` is a **key concept in two or three words** that does X. The fundamental operations are `op1` and `op2`.
Then list key properties as bullet points if applicable:
`TypeName`: - is purely functional and referentially transparent - is concurrent-safe and lock-free - updates and modifies atomically
The definition should be concise but informative, with enough detail about type parameters and variance. For example, the
Chunk[A] is an immutable, indexed sequence of elements of type A, optimized for high-performance operations.
After the definition paragraph, include the source definition of the data type in a Scala code block (using plain
```scala without mdoc, since this is for illustration):
- Show only the structural shape — the trait/class declaration with type parameters, variance annotations, and extends clauses
- Strip method bodies, private members, and extra keywords like
; show only the structural shape of the typefinal
2. Motivation / Use Case (if applicable)
This section answers the following questions:
- What is the purpose of this data type?
- What problem does it solve?
- Why was it created, and when should we use it?
- What are its key advantages over alternatives? Compare with alternatives if it helps clarify.
Tools:
- Use an ASCII art diagram showing the type structure.
- For comparing with alternatives, use a comparison table showing key differences and advantages or use bullet points to list advantages.
- Include a short code example showing the type in action — the "hello world" for this type.
3. Installation (if applicable)
Only include this for top-level module types (e.g.,
Chunk, Context, TypeId). Skip for internal types that come as part of a larger module.
libraryDependencies += "dev.zio" %% "zio-blocks-<module>" % "<version>"
For cross-platform (Scala.js):
libraryDependencies += "dev.zio" %%% "zio-blocks-<module>" % "<version>"
Note supported Scala versions: 2.13.x and 3.x.
4. Construction / Creating Instances (required)
Document all ways to create values of the type, organized by method:
- Factory methods on the companion object (
,apply
,empty
,from*
,of
)derived - Smart constructors
- Builder patterns
- Conversion from other types
- Predefined instances (if any)
Each method gets its own
### subsection with a short explanation and a code example.
5. Predefined Instances (if applicable)
If the companion object provides predefined instances (like
TypeId.int, TypeId.string), list them organized by category with a brief table or grouped code block.
6. Core Operations (Required)
Document the primary API organized by category. Group related methods under
### subsections:
For example:
- Element Access (get, apply, head, etc.)
- Transformations (map, flatMap, filter, etc.)
- Combining (++, combine, merge, etc.)
- Querying (exists, forall, find, contains, etc.)
- Conversion (toList, toArray, toString, etc.)
For each group:
- List methods with brief descriptions
- Show a code example demonstrating 2-4 methods together
- Note performance characteristics inline when relevant (e.g., "O(1)", "O(n)")
For each method:
a. Use a
heading with the method name
b. Explain what it does in plain language
c. Show the method signature in a plain ####
scala code block using the simplest trait interface format — just the method name, parameters, and return type, without extra keywords like override, final, sealed. For example:
trait Chunk[+A] { def map[B](f: A => B): Chunk[B] }
If the method is in the companion object, show it as a function in the companion object's simplest form:
object Chunk { def apply[A](as: A*): Chunk[A] }
d. Show a usage example in a compile-checked code blocks with mdoc e. Note important caveats using Docusaurus admonitions
7. Subtypes / Variants (if applicable)
If the type has important subtypes, variants, or related types (e.g.,
NonEmptyChunk for Chunk, Nominal/Alias/Opaque for TypeId), document each in a dedicated section. For each subtype:
- What it is and when to use it
- How to create it
- Key operations that differ from the parent/related type
- How to convert between the parent and subtype
8. Comparison Sections (when applicable)
Compare with analogous concepts from Java, Scala stdlib, or theoretical CS when it adds clarity. Examples:
- "Ref vs AtomicReference in Java"
- "Ref vs State Monad"
- "Promise vs Scala's Promise"
- "Chunk vs List vs Array"
- "TypeId vs Scala's TypeTag vs Java's Class"
- "Lazy vs lazy val vs def"
9. Advanced Usage / Building Blocks (when applicable)
Show how the type composes with other types or how it can be used to build higher-level abstractions.
10. Integration (if applicable)
Show how this type integrates with other ZIO Blocks data types and module. For example:
- How
is used inTypeIdSchema - How
is used inChunkReflect - How
connects toDynamicValue
and formatsSchema
Add cross-references to related docs (e.g.,
[Schema](./schema.md), [Reflect](./reflect.md)) after explaining the integration of each related type.
Compile-checked Code Blocks with mdoc
This project uses mdoc to compile-check all Scala code blocks in documentation. Every Scala code block must use one of the mdoc modifiers below. Choosing the right modifier is critical — incorrect usage causes mdoc compilation failures or broken rendered output.
Modifier Summary
| Modifier | Rendered Output | Scope | Use When |
|---|---|---|---|
| Source code only | Isolated (no shared state) | Self-contained examples where evaluated output is NOT needed |
| Nothing (hidden) | Shared with subsequent blocks | Setting up definitions needed by later blocks |
| Nothing (hidden) | Shared, wrapped in | Re-defining names already in scope |
| Source + evaluated output | Shared with subsequent blocks | When the evaluated result of expressions should be shown to the reader |
| Nothing (hidden) | Shared with subsequent blocks | Importing hidden prerequisites |
| Nothing (hidden) | Resets all prior scope | Starting a clean scope mid-document |
(no mdoc) | Source code only | Not compiled | Pseudocode, ASCII diagrams, conceptual snippets |
Key Rules
is the default for structural or setup-only examples where no output needs to be shown. Each block is compiled in isolation — definitions do NOT carry over betweenmdoc:compile-only
blocks.compile-only
defines types/values that subsequent blocks can reference (scope persists until reset). Nothing is rendered. You cannot redefine the same name — usemdoc:silent
for that.silent:nest
is likemdoc:silent:nest
but wraps code in an anonymoussilent
, allowing you to shadow names from earlier blocks (e.g., redefiningobject
with different fields in a later section).Person
wipes all accumulated scope and starts fresh. Use whenmdoc:silent:reset
wouldn't suffice (e.g., switching to a completely different topic mid-document).silent:nest
(no qualifier) shows source + evaluated output (REPL-style). Use this whenever you would otherwise writemdoc
,// Right(42L)
, or any result comment — let mdoc render the actual evaluated output instead. Requires definitions to be in scope from a prior// Some("hello")
/silent
block.silent:nest
is likemdoc:invisible
but signals "hidden imports only." Rare — prefer including imports in thesilent
block itself.compile-only- No mdoc (plain
) — not compiled. Use for pseudocode, ASCII diagrams, type signatures for illustration, or sbt/non-Scala syntax.```scala
Choosing the Right Modifier
- Self-contained example where output doesn't need to be shown? →
mdoc:compile-only - Later blocks need these definitions? →
(first time) ormdoc:silent
(redefining)mdoc:silent:nest - Need a completely clean scope? →
mdoc:silent:reset - Showing the result of expressions (return values, decoded output, computed values)? →
for setup +mdoc:silent
to render evaluated output. Never manually writemdoc
comments when mdoc can show the real output.// result - Not real Scala? → plain
or```scala```text
Pattern: Setup + Evaluated Output
When a code snippet evaluates expressions whose results are meaningful to the reader, split it into two blocks:
```scala mdoc:silent import zio.blocks.schema.Into case class Source(name: String, count: Int) case class Target(name: String, count: Long) val conv = Into.derived[Source, Target] ``` With `conv` in scope, we can call `into` and see the result: ```scala mdoc conv.into(Source("events", 100)) ```
The
mdoc block renders as:
conv.into(Source("events", 100)) // val res0: Either[zio.blocks.schema.SchemaError, Target] = Right(Target(events,100))
Do not use
mdoc:compile-only and manually write // Right(Target("events", 100L)) — always prefer the live evaluated output from mdoc.
Writing Rules
-
Be exhaustive on the public API: Every public method on the type and its companion should be documented. Group them logically, but don't skip methods.
-
One concept per code block: Each code block demonstrates one cohesive idea.
-
Always include imports: Every code block must start with the necessary import statements.
-
Show evaluated output with mdoc, not comments: When expressions have results that are meaningful to the reader (return values, decoded output, computed values), use
+mdoc:silent
so mdoc renders the real output. Do not writemdoc
or// Right(42L)
manually — these go stale and can be wrong.// Some("hello") -
Prefer
overval
: Use immutable patterns everywhere.var -
Use ASCII art for type hierarchies, data structures, and flows.
-
Link to related docs: Use relative paths
.[TypeName](./type-name.md) -
Use "ZIO Blocks" (not "zio-blocks") for the project name.
-
Don't pad: Keep prose concise. Let the code examples do the talking. Short explanatory sentence, then code block.
-
No bare subheaders: Never place a
or###
subheader immediately after a####
header with nothing in between. Always write at least one sentence of explanation before the first subheader — introduce the group, state the purpose, or give context. The same rule applies at every heading level: a heading must be followed by prose before any child heading.## -
No lone subheaders: Never create a subsection with only one child. If a
section would have only one##
, or a###
would have only one###
, remove the subheader entirely and place the content directly under the parent heading. A subheader is only justified when there are two or more siblings.#### -
Every code block must be preceded by an introductory prose sentence: The content immediately before a code block's opening fence must always be a prose sentence — never a heading alone and never blank space alone. This applies universally:
- After a heading: write at least one sentence before the first code block. Never go
→ blank line →#### Heading
.```scala - Between two consecutive code blocks: write a short bridging sentence that either summarises what the previous block set up or introduces what the next block demonstrates. Never go
→ blank line →```
.```scala - The sentence must be surrounded by blank lines on both sides (standard Markdown spacing), so the required pattern is always: prose sentence, blank line, opening
. The sentence must be concise (one line) and genuinely informative — not filler.```scala
Wrong (heading with no intro, then two bare code blocks):
#### `Option` ` ``scala implicit def optionInto[A, B](...): Into[Option[A], Option[B]] ` `` ` ``scala mdoc Into[Option[Int], Option[Long]].into(Some(42)) ` ``Right:
#### `Option` `optionInto` coerces the element type of an `Option`, passing `None` through unchanged: ` ``scala implicit def optionInto[A, B](...): Into[Option[A], Option[B]] ` `` Both `Some` and `None` are handled — `None` passes through without invoking `Into#into`: ` ``scala mdoc Into[Option[Int], Option[Long]].into(Some(42)) ` `` - After a heading: write at least one sentence before the first code block. Never go
-
Person: Use "we" when walking through examples or any time you want to guide the reader through a process or example. ("we can create...", "we need to...").
-
Tense: Use present tense ("returns", "creates", "modifies").
-
Code snippet description: When showing example code snippets, explain what they do and why they are relevant. Don't just show code without context.
-
Referencing types, operations, and constructors: Apply these conventions consistently in all prose, section headings, and inline code:
- Type name alone — when talking about the type itself, use only its name with no qualifier: "derives automatically via
", "As
is a one-way conversion".Into - Instance method — use
(theTypeName#methodName
convention signals a non-static member):#
,As#from
,As#into
,As#reverse
.Into#into - Companion object operation or constructor — use
(theTypeName.methodName
convention signals a companion/static member):.
,As.derived
,As.apply
,Into.derived
.Into.apply
- Type name alone — when talking about the type itself, use only its name with no qualifier: "derives automatically via
Docusaurus Admonitions
Use Docusaurus admonition syntax for adding any of
note, tip, info, warning, and danger to highlight important information. Example for a note:
:::note Additional context or clarification. :::
Step 3: Write Examples
Create focused
App objects in zio-blocks-examples/src/main/scala/<type-name-lowercase>/. Each App demonstrates one use case. Avoid bundling unrelated scenarios into a single App.
File granularity
- One
per concept — schema evolution, collection reshaping, error accumulation, etc. are separateApp
objects.App - Small, related
s can share a file — if severalApp
s are short and tightly related (e.g., numeric widening variants), place them together in one file so the reader can run them in sequence.App - Large
s get their own file — if anApp
needs many types or substantial setup, give it a dedicated file.App
Conventions
- Package: matches the directory name (e.g.,
forpackage into
)into/ - Object:
so each unit is independently runnableextends App - Output: use
to print both the expression and its result — e.g.,util.ShowExpr.show(expr)
printsshow(Into[Int, Long].into(100))
. Never print just the result alone; the reader should see what was evaluated without looking at the source. TheInto[Int, Long].into(100) => Right(100)
helper lives inshow
and is powered byzio-blocks-examples/src/main/scala/util/ShowExpr.scala
to capture the source text at compile time.sourcecode.Text - Naming: name files after the scenario(s) they contain (e.g.,
,IntoSchemaEvolutionExample.scala
) not just the type nameIntoCollectionsExample.scala
What to Cover
Each
App should:
- Focus on one coherent use case
- Use realistic domain types (
,Person
,Order
) rather than abstractAddress
/SourceTarget - Cover the happy path and at least one failure/edge case
- Be self-contained — all types and imports are defined within the file
Example: multiple small Apps in one file
package mytype import zio.blocks.schema.Into import util.ShowExpr.show // Small related examples share a file — reader runs them one after another object IntoWideningExample extends App { show(Into[Int, Long].into(100)) show(Into[Float, Double].into(3.14f)) } object IntoNarrowingExample extends App { show(Into[Long, Int].into(42L)) show(Into[Long, Int].into(Long.MaxValue)) }
Example: large App in its own file
// IntoSchemaEvolutionExample.scala package mytype import zio.blocks.schema.Into import util.ShowExpr.show object IntoSchemaEvolutionExample extends App { case class PersonV1(name: String, age: Int) case class PersonV2(name: String, age: Long, email: Option[String]) val migrate = Into.derived[PersonV1, PersonV2] show(migrate.into(PersonV1("Alice", 30))) show(migrate.into(PersonV1("Bob", 25))) }
Step 4: Integrate
After writing the reference page and examples:
- If updating an existing file, edit it in place.
- If creating a new file, place it in the appropriate
subdirectory based on where it logically belongs and updatedocs/reference/
to add it to the sidebar.sidebars.js - Update
: Add the new page under the appropriate section in the "Documentation" heading.docs/index.md - Cross-reference: Add links from related existing docs to the new page.
- Verify all links: Ensure relative links in the new page and in updated pages are correct.
Step 5: Review
After writing, re-read the document and verify:
- All method signatures match the actual source code
- All code examples would compile with
mdoc - The frontmatter
matches whatid
expects (if an entry exists)sidebars.js - The document is self-contained—a reader shouldn't need to look at the source code to understand the type's API
- The example file compiles and runs without errors