diff --git a/.env.local.example b/.env.local.example
new file mode 100644
index 0000000..3b375bc
--- /dev/null
+++ b/.env.local.example
@@ -0,0 +1,23 @@
+# Fluree Documentation - Claude Code Environment Variables
+#
+# These variables enable enhanced documentation verification when using Claude Code.
+# They are NOT used by Docusaurus — they're read by Claude Code from your shell environment.
+#
+# To use: Add these exports to your shell profile (.zshrc, .bashrc, etc.)
+# or source this file before starting Claude Code:
+#
+# source .env.local.example # after filling in values
+#
+# See CLAUDE.md for full documentation.
+
+# URL of a running Fluree server for testing documentation examples
+# Example: http://localhost:58090
+export FLUREE_TEST_SERVER_URL=
+
+# Path to local fluree/db repository for source code reference
+# Example: /Users/yourname/projects/fluree/db
+export FLUREE_DB_REPO_PATH=
+
+# Path to local fluree/server repository for source code reference
+# Example: /Users/yourname/projects/fluree/server
+export FLUREE_SERVER_REPO_PATH=
diff --git a/.gitignore b/.gitignore
index 1f05e6a..48eaea7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,7 @@ build
.docusaurus
bin
-.DS_Store
\ No newline at end of file
+.DS_Store
+
+# Local environment variables (copy .env.local.example to .env.local)
+.env.local
\ No newline at end of file
diff --git a/.tonal-references/working-with-graph-data.mdx b/.tonal-references/working-with-graph-data.mdx
new file mode 100644
index 0000000..61eaa79
--- /dev/null
+++ b/.tonal-references/working-with-graph-data.mdx
@@ -0,0 +1,558 @@
+import Admonition from "@theme/Admonition";
+import { FlureeMermaid } from "@site/src/components/Mermaid/FlureeMermaid.jsx";
+
+# Working with Graph Data
+
+The last chapter focused on how you can describe individual entities using RDF
+triples, how those triples form a graph, and how you can use JSON to encode
+triples. This chapter refines our notion of "entity" and shows how you can use
+JSON to encode relationships between entities.
+
+Along the way, we'll focus on a problem: How do you represent the following
+graph in JSON? How would you insert it into Fluree?
+
+|"@id"| jid(_:f100)
+ j -->|name| jn(Jack)
+ j -->|species| sp1(Mongolian death worm)
+ j -->|bestFriend| l
+ l(_:f101) -->|"@id"| lid(_:f101)
+ l -->|name| ln(Lucia)
+ l -->|species| sp2(Mongolian death worm)
+ l -->|bestFriend| j
+`}
+/>
+
+This shows two Mongolian death worms named `Jack` and `Lucia` who have a
+reciprocal `bestFriend` relationship. JSON on its own has no standard way for
+representing this kind of relationship. You'd need some way for `_:f100` to
+refer to `_:f101`, and while you could come up with a convention for doing that,
+it would be idiosyncratic to your system and difficult to enforce and maintain.
+
+This chapter will show you how to use JSON-LD, a W3C standard, to represent this
+relationship -- and virtually any other graph. It will show you how store these
+graphs in Fluree.
+
+## Representing entities
+
+Before we enter the realm of JSON-LD, I first need to clarify some subtleties
+around how we use the word "entity." Many of us think of an entity as similar to
+a _row_ in a relational database or an _object_ in an object oriented language.
+An entity has _fields_ (if it's in a db) or _attributes_ (if it's an OO object),
+and those fields/attributes have _values_. There's a distinction between the
+entity as a data container (row or object), and the data that it contains.
+
+RDF does not technically support this kind of distinction. It's all just nodes
+connected by arcs, resources connected by predicates. Take this graph:
+
+In the triple `["Jack", "loves", "cheesecake"]`, none of the elements can be
+said to have the privileged position of "entity" as something which encompasses
+the other elements. This notion is reinforced when you visualize the triple:
+
+|loves| c(cheesecake)
+`}
+/>
+
+There's no container here, just two nodes and an arc. The arc does have a
+direction, from Jack to cheesecake, but there's nothing stopping you from
+reading this as "cheesecake is loved by Jack" or from adding an arc from
+cheesecake to Jack.
+
+Nevertheless, the notion of an entity as a container is useful and it feels
+intuitive. If we're modeling customer data, we want to be able to talk about _a
+customer_ as an actual thing that has properties or attributes. The resolution
+here is to just acknowledge that you can impose that kind of organization onto
+an RDF graph, _but_ it's a convenience for your thinking and communication, and
+_not_ something that's technically reflected in the way RDF data is structured.
+
+We also need to contend with the practical fact that we are representing our
+data using JSON objects, which actually _are_ containers. How do we bridge this
+gap between RDF's notion of data as a non-hierarchical collection of nodes and
+arcs, and JSON's inherently hierarchical structure?
+
+When we say that a JSON object represents an entity, what we mean is that the
+JSON object's key/value pairs correspond to RDF predicates and objects in a set
+of triples, and all of those triples have the same subject.
+
+That gets us part of the way to bridging the gap. But what is the subject? How
+do we even specify that? Take this JSON:
+
+{/* */}
+
+```json
+{
+ "name": "Jack",
+ "species": "Mongolian death worm"
+}
+```
+
+The key/value pairs correspond to RDF predicates and objects, so where would we
+even specify a subject? As it turns out, there's a W3C standard that defines how
+to fully represent RDF data using JSON data structures, including how to specify
+the subject in a JSON object. It's called JSON-LD which stands for _JSON for
+Linked Data_.
+
+## JSON-LD
+
+JSON-LD is a standard for representing RDF data using JSON data structures.
+Because RDF data describes graphs, JSON-LD can also be thought of as a way to
+represent graphs using JSON. I'll talk about JSON-LD from both perspectives.
+
+JSON-LD is just JSON, but with some additional rules for structuring objects to
+provide the information machines and humans need to interpret the JSON as a
+graph. When I say _a JSON-LD object_, I mean a JSON object that's being
+interpreted using the rules of the JSON-LD standard.
+
+These rules are necessary because JSON on its own doesn't provide us with a
+standard way of representing the kinds of relationships we want to represent,
+like the mutual `"bestFriend"` relationship between Jack and Lucia from earlier.
+With JSON-LD, though, you can represent virtually any graph.
+
+This is useful in itself; using JSON-LD also gives you a couple strategic
+advantages:
+
+1. Because it's built on top of JSON, you can leverage the wealth of existing
+ tools for working with JSON data.
+2. Because it's a standard, JSON-LD-structured data is portable and composable
+ in a way that simply isn't possible otherwise. It provides a common language
+ for representing information across systems, removing the need to constantly
+ write software that translates between data system.
+
+In the same way that JSON is a data format that's defined independently from any
+particular application, JSON-LD is a way to represent graphs that's defined
+independently of any database, including Fluree. Fluree provides a JSON-LD
+interface, meaning that the data you insert and update is formatted as JSON-LD,
+queries you write (and the results they return) are too. To understand how to use Fluree, we'll need to
+learn JSON-LD.
+
+## Inserting JSON-LD Data
+
+Let's return to the question of how we indicate the RDF subject in a JSON
+object. With JSON-LD, you use the `"@id"` key:
+
+{/* */}
+
+```json
+{
+ "@id": "_:f100",
+ "name": "Jack",
+ "species": "Mongolian death worm"
+}
+```
+
+
+
+In real-world usage you'll want to use _IRIs_ for `"@id"` values, a topic
+we'll cover in the next chapter. IRIs typically look like URIs, with values
+like `"http://cryptid-research.com/researchers/jack"`.
+
+We'll keep formatting @id's using the `_:` prefix for the time being, but if
+for some reason you stop the tutorial here just be aware that it'll be
+important to learn how to work with IRIs.
+
+For those familiar with RDF, note that Fluree treats blank node identifiers as
+stable identifiers, such that if you run multiple transactions using the same
+blank node id for `@id`, then it will modify a single subject rather than
+inserting new subjects.
+
+
+
+This corresponds to the following graph:
+
+|name| jn(Jack)
+ j -->|species| s1("Mongolian death worm")
+`}
+/>
+
+So that answers the question of how to encode the subject in a JSON object, but
+you're probably wondering about where we got the string `"_:f100"`, and what
+role Fluree plays in creating these values. After all, in other database systems
+there are mechanisms for generating unique primary keys; how does this work in Fluree?
+
+The answer actually has a surprising amount of depth to it, but for now I'm just
+going to focus on the bare minimum you need to know in order to understand how
+to represent the `"bestFriend"` relationship:
+
+1. Because JSON-LD is a strict serialization of RDF triples, and because RDF triples always have a subject, every entity in JSON-LD (and in Fluree) must have an `"@id"` value, even if none is explicitly supplied.
+2. You don't need to provide an `"@id"` value explicitly. If you don't, Fluree will generate one for you. These data entities are known as _blank nodes_, and Fluree's `@id` assignment follows a pattern of **`_:f[integer value]`**.
+3. If you do provide an `"@id"` value, this value (conventionally, a URI string representing an entity with global uniqueness) becomes an identifier that makes it easier to qualify your updates to (or your queries against) that node in the future.
+4. When we transact facts against an entity by referencing its `"@id"` value, those updates effectively qualify _upserts_ against that entity. If an entity with that subject IRI already exists, the facts will be applied to it. If it doesn't exist, that entity will be created.
+5. If you include an `"@id"` value in your JSON object, its value does not have
+ to match Fluree's naming scheme, and its value doesn't need to already exist
+ in the database. That is to say: you can generate new identifiers using your
+ own system in order to create new entities in Fluree.
+
+Let's look at the following transactions (and feel free to try them out directly in the sandbox!)
+
+
+ In all of the following examples we will be using Fluree's `insert` key to
+ assert the following information. We are also able to combine the `insert` key
+ with the `delete` key to issue more nuanced updates to existing data, and we
+ can even use the `where` key to bind existing data to `?logicVariables`,
+ making it possible to execute all kinds of surgical, precise updates to new or
+ existing data.
+
+
+{/* */}
+
+```jsonc
+// This will insert or update the following facts about
+// the entity with the subject IRI, "http://example.org/jack"
+
+{
+ "insert": {
+ "@id": "http://example.org/jack",
+ "name": "Jack",
+ "species": "Mongolian death worm"
+ }
+}
+```
+
+{/* */}
+
+```jsonc
+// This will insert the same predicate-object pairs as the
+// previous transaction, but because no @id is provided, Fluree
+// will generate a new blank node entity with an arbitrary @id IRI
+
+{
+ "insert": {
+ "name": "Jack",
+ "species": "Mongolian death worm"
+ }
+}
+```
+
+{/* */}
+
+```jsonc
+// We see here the same predicate-object facts as above, but
+// with an entirely new subject IRI, "http://example.org/some-new-identifier".
+// Nodes are directly identified by their @id subject IRI, not by other
+// predicate-object pairs, so this transaction will not affect or update
+// data on "http://example.org/jack"
+
+{
+ "insert": {
+ "@id": "http://example.org/some-new-identifier",
+ "name": "Jack",
+ "species": "Mongolian death worm"
+ }
+}
+```
+
+
+ We also have a pattern for updating data on existing entities without using
+ their `@id` subject IRIs as identifiers. We'll look at this later, but it
+ involves issuing a kind of subquery with the `where` key to find entities
+ matching particular data conditions, and then using our `?logicVariable`
+ patterns to update data on the results of those subqueries.
+
+
+So that's how we represent a single entity with JSON-LD. To represent our best
+friend graph, though, we'll need to represent multiple entities. Here's how to
+do that:
+
+{/* */}
+
+```json
+{
+ "insert": [
+ {
+ "@id": "_:f100",
+ "bestFriend": {
+ "@id": "_:f101"
+ },
+ "name": "Jack",
+ "species": "Mongolian death worm"
+ },
+ {
+ "@id": "_:f101",
+ "bestFriend": {
+ "@id": "_:f100"
+ },
+ "name": "Lucia",
+ "species": "Mongolian death worm"
+ }
+ ]
+}
+```
+
+With JSON-LD, you can use an array to represent a collection of entities. This
+is how we can use JSON to represent any number of nodes and relationships in a
+graph.
+
+`"@id"` is a _keyword_, meaning that JSON-LD assigns them special meaning that's
+not present solely in the data itself. `"@id"` is still a JSON key like `"name"`
+or any other key, but tools that interpret JSON as JSON-LD (like Fluree) know
+that the key has additional significance, designating an identifier that should
+be used as the subject for a set of triples.
+
+This is generally what it means for JSON-LD to be a standard implemented on top
+of JSON. JSON-LD defines keywords and their possible values so that applications
+will have a clear way of translating JSON into internal data structures.
+
+Note the value of `"bestFriend"`: it's not simply the string for the `"@id"`
+being referenced, it's a JSON object with the key `"@id"`. It's not this:
+
+```json
+{ "bestFriend": "_:f100" }
+```
+
+It's this:
+
+```json
+{
+ "bestFriend": {
+ "@id": "_:f100"
+ }
+}
+```
+
+Any time you want to reference an identifier, make sure you do it in this way.
+
+You can transact multiple entities with Fluree in an array, and Fluree will insert
+all entities, meaning that it will create triples for all the key/value pairs in
+the JSON object. If you were to transact the JSON with the death worms above, Fluree
+would create the following triples:
+
+{/* */}
+
+```json
+[
+ ["_:f100", "@id", "_:f100"],
+ ["_:f100", "name", "Jack"],
+ ["_:f100", "species", "Mongolian death worm"],
+ ["_:f100", "bestFriend", "_:f101"],
+ ["_:f101", "@id", "_:f101"],
+ ["_:f101", "name", "Lucia"],
+ ["_:f101", "species", "Mongolian death worm"],
+ ["_:f101", "bestFriend", "_:f100"]
+]
+```
+
+Thus, it's possible to insert multiple entities at the same time, and for the
+entities to refer to each other.
+
+## Querying graph data
+
+Graphs have no logical beginning or end, nor do they have containers in the way
+we're used to with other databases. Yet, it's useful to represent them in JSON,
+which imposes a bounded, hierarchical structure on the data. We've explored this
+from the perspective of transactions but how do we represent and issue queries by using JSON-LD?
+
+As a reminder from the last chapter, when you execute a query, it's like you're telling Fluree how to:
+
+1. Filter Fluree's nodes down to some initial set of nodes
+2. Recursively include adjacent arcs and nodes
+3. Return the selected nodes and arcs as JSON
+
+### A simple query
+
+Let's start with a simple query:
+
+{/* */}
+
+```json
+{
+ "select": {
+ "?s": ["*"]
+ },
+ "where": {
+ "@id": "?s",
+ "bestFriend": "?friend"
+ }
+}
+```
+
+If you've inserted the two Mongolian death worm entities above, you should get a
+result that looks like this:
+
+```json
+[
+ {
+ "@id": "_:f101",
+ "bestFriend": {
+ "@id": "_:f100"
+ },
+ "name": "Lucia",
+ "species": "Mongolian death worm"
+ },
+ {
+ "@id": "_:f100",
+ "bestFriend": {
+ "@id": "_:f101"
+ },
+ "name": "Jack",
+ "species": "Mongolian death worm"
+ }
+]
+```
+
+What is the relationship between the query and the results? As in the last
+chapter, the `"where"` clause is responsible for filtering down Fluree's set of
+nodes to those that meet some criteria, and for binding those nodes to a logic
+variable.
+
+Here, the object `{ "@id": "?s", "bestFriend": "?friend" }` selects those nodes that have
+any value on the predicate named `"bestFriend"`, regardless of what that value may be. It binds the subject IRIs of those nodes to the `"?s"` logic variable. In our example, the
+`"_:f100"` and `"_:f101"` node identifiers get bound to `"?s"`.
+
+
+ In this example, we create a `?friend` logic variable in our `where` clause,
+ but we aren't using the values bound to `?friend` within the `select`
+ statement where we shape the projection of our query results. Here, it's
+ simply being used to express that we care about nodes that have any possible
+ value on the predicate, `bestFriend`, but we don't want to limit our result
+ set by pre-specifying what that value should be.
+
+
+When we use subject IRI logic variables from our `where` clause to project results via our `select` clause syntax,
+we need to tell Fluree how to represent the nodes that are bound to those logic variables.
+
+We could say, `"select": { "?s": ["*"] }` if we wanted Fluree to crawl every property on the subjects bound to `?s` and represent each property-object value as a JSON key-value pair, or we could be more explicit in the property-object pairs we need returned from this query, for example `"select": { "?s": ["name", "species"] }`, if we only wanted JSON objects returned with the `name` and `species` properties.
+
+Fluree will construct a JSON object for each of the selected nodes, and it will
+use the `"select"` clause to determine which keys and values to include in the
+JSON object.
+
+Let's put this all together. Fluree is storing this graph:
+
+|"@id"| jid(_:f100)
+ j -->|name| jn(Jack)
+ j -->|species| sp1(Mongolian death worm)
+ j -->|bestFriend| l
+ l(_:f101) -->|"@id"| lid(_:f101)
+ l -->|name| ln(Lucia)
+ l -->|species| sp2(Mongolian death worm)
+ l -->|bestFriend| j
+`}
+/>
+
+The `"where"` clause of your query uses the `?s` logic variable, and given our pattern that looks for any `?s` with any value on the property, `bestFriend`, it binds the `"_:f100"` and `"_:f101"` node IRIs to `?s`, because they are the only nodes with an outgoing arc named `"bestFriend"`. The `"select"` clause specifies
+that you want to build a JSON object for each of these nodes such that every
+outgoing arc (and its corresponding value) is encoded as key/value pairs in the
+JSON object.
+
+Thus, the query results include two JSON objects, one for each node. Each JSON
+object includes the keys `"name"`, `"species"`, and `"bestFriend"` (as well as the JSON key-value pair `@id`, because even though this represents the _subject_ identifier and is not strictly a _property_, JSON has no other way to represent this than with the JSON-LD `@id` key).
+
+The value of `"bestFriend"` is not just the string `"_:f100"` or `"_:f101"`.
+Instead, it's the object `{"@id": "_:f100"}`. `"@id"` values are always encoded
+this way.
+
+### Graph-Crawling in our Select Clauses
+
+There's a practical reason for this: you might want to populate that object with
+more key/value pairs. Check out this query and the result:
+
+{/* */}
+
+```json
+{
+ "select": {
+ "?s": [
+ "name",
+ {
+ "bestFriend": ["*"]
+ }
+ ]
+ },
+ "where": {
+ "@id": "?s",
+ "bestFriend": "?friend"
+ }
+}
+```
+
+Result:
+
+```json
+[
+ {
+ "name": "Lucia",
+ "bestFriend": {
+ "@id": "_:f100",
+ "name": "Jack",
+ "species": "Mongolian death worm",
+ "bestFriend": {
+ "@id": "_:f101"
+ }
+ }
+ },
+ {
+ "name": "Jack",
+ "bestFriend": {
+ "@id": "_:f101",
+ "name": "Lucia",
+ "species": "Mongolian death worm",
+ "bestFriend": {
+ "@id": "_:f100"
+ }
+ }
+ }
+]
+```
+
+Whereas in the previous queries, we saw the graph data expanded for the `?s` nodes, but not for any additional entities downstream of those nodes, in this query we are explicitly including in our `select` clause that if a property, `bestFriend` exists, and if the value(s) of this property are, themselves, subject identifiers for other nodes, then return all data (`["*"]`) on those downstream nodes.
+
+The `"select"` clause that yields this result is `["name", {"bestFriend":
+["*"]}]`. The `"name"` portion works just as described before: it directs Fluree
+to include the `"name"` property-object arc for the selected subject. The object `{"bestFriend":
+["*"]}` is where things get interesting.
+
+Let's call this object a _node object template_. The keys of a node object
+template (`"bestFriend"`) correspond to arcs, and the values are arrays that
+specify how to build a JSON object for the arcs' nodes (the node that
+`"bestFriend"` points to).
+
+The node object template `{"bestFriend": ["*"]}` is saying, _include the
+bestFriend arc. Use a JSON object to represent the node that the bestFriend arc
+points to. To construct that JSON object, include all of the node's outgoing
+arcs._
+
+In my mind, I think of this as Fluree using the arcs you specify to travel to
+each node and then running a little recipe to determine how to represent that
+node. With the array `["name", {"bestFriend": ["*"]}]`, Fluree travels down the
+`"name"` arc to a value node. Because `"name"` is a simple string, the recipe is "just
+return the value of this node". Next, Fluree travels down the `"bestFriend"`
+arc. Because `"bestFriend"` is specified with a node object template, the recipe
+is "construct a JSON object using the array `["*"]`".
+
+Fluree then travels down each outgoing arc from the `"bestFriend"` node. It
+travels down `"name"`, `"species"`, and `"bestFriend"` arcs, and this
+time the recipe is "just return the value of this node."
+
+### Data in the shape you need it
+
+Fluree's combination of graph data and flexible querying brings a benefit that
+isn't obvious: it's a lot easier to get data in the shape that your application
+needs it, rather than having to go through extra processing steps to translate
+between the data structures your database returns and the ones your app actually
+needs. For more examples of data in different shapes and more complex queries,
+see [the reference documentation for the Fluree Query Syntax](/docs/reference/flureeql-query-syntax/).
+
+## Summary
+
+- [JSON-LD is a W3C standard](https://www.w3.org/TR/json-ld/) that defines how
+ to represent RDF graphs using JSON
+- JSON-LD keywords are JSON keys that are given special meaning in a JSON-LD
+ context
+- You use the `"@id"` key to signify the _subject_ for a JSON-LD object
+- You use arrays to encode multiple JSON-LD entities
+- Fluree queries specify:
+ 1. How to filter Fluree's nodes down to some initial set of nodes
+ 2. Which nodes and arcs to include in the JSON that's returned
+ 3. How to transform the selected nodes and arcs into JSON
+
+{/* */}
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..5ae95db
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,297 @@
+# Fluree Developers Site
+
+Documentation site for Fluree built with Docusaurus 3.9.2.
+
+## Project Structure
+
+```
+docs/
+├── getting-started/ # Quick start, installation, core concepts
+├── learn/ # Conceptual documentation
+│ ├── foundations/ # Core concepts (graph data, JSON-LD, context)
+│ ├── working-with-data/ # CRUD operations, queries, time travel
+│ ├── modeling/ # Classes, properties, ontologies, SHACL, reasoning
+│ ├── security/ # Access control, policies, verifiable data
+│ └── advanced/ # Architecture, language tags, etc.
+├── reference/ # API and syntax reference
+│ ├── http-api/ # HTTP endpoint documentation
+│ ├── querying/ # FlureeQL query syntax (split by topic)
+│ └── cookbook/ # Examples and patterns
+├── examples/ # Example datasets and walkthroughs
+├── cloud/ # Fluree Cloud platform docs
+└── sense/ # Fluree Sense product docs (separate product)
+```
+
+## Tone & Voice
+
+Documentation should be **warm, enthusiastic, and welcoming** — inviting users to learn the material rather than lecturing at them. Think of the tone in *Clojure for the Brave and True* by Daniel Higginbotham as inspiration.
+
+### Guidelines
+
+- **Explain the "why"** — Don't just show syntax; help readers understand the reasoning behind design decisions
+- **Don't assume RDF/JSON-LD knowledge** — Many users come from SQL backgrounds; meet them where they are
+- **Use second person** — "You can query..." not "One can query..."
+- **Be encouraging** — Acknowledge when concepts are tricky, then guide through them
+- **Keep it conversational** — Avoid dry, academic prose
+
+### Tonal Reference
+
+The original tutorial guide by Daniel Higginbotham is preserved at `.tonal-references/working-with-graph-data.mdx` as an example of the desired tone. Key elements:
+
+- Uses engaging examples (Mongolian death worms named Jack and Lucia)
+- Acknowledges complexity without being intimidating
+- Builds concepts progressively with clear explanations
+- Conversational without being sloppy
+
+When writing new content, read this file first to calibrate your voice.
+
+## Documentation Coherence
+
+When creating or editing documentation:
+
+1. **Reference existing docs** — Read related guides to ensure consistency in terminology, syntax examples, and explanations
+2. **Cross-link generously** — When mentioning topics covered elsewhere, link to them. The docs should form a cohesive, interconnected whole
+3. **Build on prior knowledge** — Later guides should reference concepts from earlier ones rather than re-explaining everything
+4. **Maintain consistency** — Use the same example patterns, namespace prefixes, and entity names across related guides when it aids understanding
+
+## Code Example Standards
+
+### Copy-Paste Readiness
+
+Code examples should generally be **complete and runnable** — users should be able to copy-paste them directly. This means:
+
+- Include full `@context` blocks where needed
+- Show complete JSON-LD documents, not fragments
+- Include the `from` or `ledger` field in queries/transactions
+
+**Exceptions**: Some guides specifically teach concepts like `@context` or show incremental building of queries. In these cases, incomplete examples are appropriate when they serve the pedagogical goal.
+
+### Code-Hike Focus Annotations
+
+This project uses [Code-Hike](https://codehike.org/) for enhanced code blocks. Use the `focus` annotation to highlight specific lines while still showing complete examples:
+
+```mdx
+```json focus=3:5
+{
+ "@context": {"ex": "http://example.org/"},
+ "@id": "ex:alice",
+ "ex:name": "Alice",
+ "ex:age": 30
+}
+```
+```
+
+This keeps examples copy-paste ready while drawing attention to what matters. Available focus syntax:
+
+- `focus=3` — Single line
+- `focus=3:5` — Line range
+- `focus=3,7,9` — Multiple specific lines
+- `focus=3:5,8:10` — Multiple ranges
+
+You can also use inline focus links in explanatory text: `[the @id field](focus://3[3:15])` highlights specific characters.
+
+## Documentation Conventions
+
+### File Formats
+
+- Use `.md` for simple markdown files
+- Use `.mdx` for files requiring React components, Code-Hike features, or Mermaid diagrams
+
+### Code Fence Languages
+
+- JSON examples: `json`
+- Shell/curl examples: `sh`
+- SPARQL: `sparql`
+- Turtle/RDF: `turtle`
+- Clojure: `clojure`
+- JavaScript: `javascript`
+- Python: `python`
+
+### API Reference Format
+
+For HTTP endpoints, follow this structure:
+
+1. Endpoint heading (`## \`fluree/endpoint\``)
+2. HTTP method block (` ```POST /fluree/endpoint``` `)
+3. Request Object table (Key | Required | Value)
+4. Example Request Object (JSON)
+5. Curl Example
+6. Example Response
+
+### Tables
+
+Use GitHub-flavored markdown tables:
+
+```md
+| Key | Required | Value |
+| ---------- | -------- | ------------------------------- |
+| `@context` | no | **object** • description |
+| `ledger` | yes | **string** • description |
+```
+
+### Admonitions
+
+Use blockquotes with **NOTE**, **WARNING**, **TIP** prefixes:
+
+```md
+> **NOTE**: Important information here.
+```
+
+### Internal Links
+
+Use relative paths with `/docs/` prefix:
+
+```md
+[Link Text](/docs/reference/http-api)
+```
+
+### Mermaid Diagrams
+
+Supported via `@docusaurus/theme-mermaid`. Use in `.mdx` files:
+
+````md
+```mermaid
+graph LR
+ A --> B
+```
+````
+
+## Sidebar Configuration
+
+Sidebars are configured in `sidebars.js`. Main sections:
+
+- `gettingStarted` - Quick start, installation, core concepts
+- `learn` - Foundations, Working with Data, Modeling, Security, Advanced
+- `reference` - HTTP API, Query syntax, Transaction syntax, Cookbook
+- `examples` - Example datasets by domain and feature
+- `cloud` - Fluree Cloud docs
+- `SenseSidebar` - Fluree Sense docs
+
+## Quality Checks
+
+Before finalizing documentation changes:
+
+### Content Review
+
+- [ ] Read through the guide as a new user would — does it flow logically?
+- [ ] Are all concepts explained before they're used?
+- [ ] Are prerequisites clearly stated?
+- [ ] Do examples build on each other sensibly?
+
+### Link Validation
+
+- [ ] All internal links resolve (not pointing to deleted/moved pages)
+- [ ] External links are valid and relevant
+- [ ] Anchor links (`#section-name`) point to existing headings
+
+### Build Verification
+
+```sh
+bun run build
+```
+
+The build will fail on broken markdown links (`onBrokenLinks: "throw"` in config). Fix any errors before committing.
+
+### Cross-Reference Check
+
+- [ ] Related guides link to each other appropriately
+- [ ] No orphaned pages (every page reachable from sidebar)
+- [ ] New pages added to `sidebars.js`
+
+## Development
+
+```sh
+bun install # Install dependencies
+bun start # Start dev server (hot reload)
+bun run build # Production build
+```
+
+## Verifying Documentation with Claude Code (Optional)
+
+Contributors using [Claude Code](https://docs.anthropic.com/en/docs/claude-code) can enable enhanced documentation verification. These features are entirely optional — contributors who don't use Claude Code can ignore this section.
+
+### Testing Code Examples
+
+Claude can verify that documentation examples actually work by running them against a live Fluree instance.
+
+**Setup:**
+1. Start a Fluree server locally (see [Getting Started](/docs/getting-started/installation))
+2. Set the environment variable:
+ ```sh
+ export FLUREE_TEST_SERVER_URL="http://localhost:58090"
+ ```
+3. Ask Claude to verify examples: *"Can you test the examples in this guide against Fluree?"*
+
+**Behavior:**
+- If `FLUREE_TEST_SERVER_URL` is set, Claude will run transactions and queries via curl and report whether they succeed
+- If not set, Claude will note: *"I can verify these examples if you start a Fluree server and set `FLUREE_TEST_SERVER_URL`"*
+
+### Exploring Fluree Source Code
+
+Claude can reference the `fluree/db` and `fluree/server` repositories to verify that documentation matches actual implementation.
+
+**Option 1: Local repositories** (if you have them cloned)
+
+Set environment variables pointing to your local clones:
+
+```sh
+export FLUREE_DB_REPO_PATH="/path/to/fluree/db"
+export FLUREE_SERVER_REPO_PATH="/path/to/fluree/server"
+```
+
+Claude will use its built-in file tools to search and read code directly.
+
+**Option 2: GitHub MCP Server** (no local clone needed)
+
+If you don't want to clone the repos, you can add the [GitHub MCP Server](https://github.com/github/github-mcp-server) to your Claude Code configuration. This lets Claude search and read code directly from GitHub.
+
+Add to your Claude Code MCP settings:
+
+```json
+{
+ "mcpServers": {
+ "github": {
+ "command": "docker",
+ "args": [
+ "run", "-i", "--rm",
+ "-e", "GITHUB_PERSONAL_ACCESS_TOKEN",
+ "ghcr.io/github/github-mcp-server"
+ ],
+ "env": {
+ "GITHUB_PERSONAL_ACCESS_TOKEN": ""
+ }
+ }
+ }
+}
+```
+
+> **NOTE**: A GitHub Personal Access Token is optional for public repos but recommended to avoid rate limits. Create one at https://github.com/settings/personal-access-tokens with `repo` scope.
+
+### Environment Variable Summary
+
+| Variable | Purpose |
+|----------|---------|
+| `FLUREE_TEST_SERVER_URL` | URL of running Fluree server for testing examples |
+| `FLUREE_DB_REPO_PATH` | Path to local `fluree/db` clone for source reference |
+| `FLUREE_SERVER_REPO_PATH` | Path to local `fluree/server` clone for source reference |
+
+### Quick Setup
+
+For contributors who want the full experience:
+
+```sh
+# Add to your shell profile (.zshrc, .bashrc, etc.)
+export FLUREE_TEST_SERVER_URL="http://localhost:58090"
+export FLUREE_DB_REPO_PATH="/path/to/fluree/db"
+export FLUREE_SERVER_REPO_PATH="/path/to/fluree/server"
+```
+
+Or copy and fill in `.env.local.example`, then source it:
+
+```sh
+cp .env.local.example .env.local
+# Edit .env.local with your values
+source .env.local
+```
+
+Then when working on docs, start Fluree and ask Claude to verify your examples.
diff --git a/docusaurus.config.js b/docusaurus.config.js
index b7d0035..2d100e5 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -62,17 +62,24 @@ module.exports = {
position: "left",
className: "navbar-home-link",
},
+ {
+ label: "Getting Started",
+ to: "/getting-started",
+ docId: "getting-started/index",
+ type: "doc",
+ position: "left",
+ },
{
label: "Learn",
to: "/learn",
- docId: "learn/overview",
+ docId: "learn/index",
type: "doc",
position: "left",
},
{
label: "Reference",
- to: "/refence",
- docId: "reference/http-api",
+ to: "/reference",
+ docId: "reference/http-api/index",
type: "doc",
position: "left",
},
@@ -143,8 +150,8 @@ module.exports = {
title: "Docs",
items: [
{
- label: "Tutorial",
- to: "docs/learn/tutorial/introduction/",
+ label: "Learn",
+ to: "/docs/learn/",
},
{
label: "Examples",
@@ -243,12 +250,13 @@ module.exports = {
],
],
},
- gtag: {
- // You can also use your "G-" Measurement ID here.
- trackingID: "G-XDLKYE388T",
- // Optional fields.
- anonymizeIP: true, // Should IPs be anonymized?
- },
+ gtag:
+ process.env.NODE_ENV === "production"
+ ? {
+ trackingID: "G-XDLKYE388T",
+ anonymizeIP: true,
+ }
+ : false,
// blog: {
// showReadingTime: true,
// // TODO: Please change this to your repo.
diff --git a/package.json b/package.json
index 747280e..8856904 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"docusaurus": "docusaurus",
- "start": "docusaurus start",
+ "start": "docusaurus start --port 3210",
"build": "docusaurus build",
"dev": "docusaurus start --port 3001 --no-open",
"swizzle": "docusaurus swizzle",
diff --git a/sidebars.js b/sidebars.js
index 7befd21..b6e6267 100644
--- a/sidebars.js
+++ b/sidebars.js
@@ -456,53 +456,285 @@ module.exports = {
]
}
],
+ "getting-started": [
+ "getting-started/index",
+ "getting-started/quick-start",
+ "getting-started/installation",
+ "getting-started/core-concepts",
+ ],
learn: [
- "learn/overview",
+ "learn/index",
{
- Tutorial: [
- "learn/tutorial/introduction",
- "learn/tutorial/fluree-data-model",
- "learn/tutorial/working-with-graph-data",
- "learn/tutorial/collaborative-data",
+ type: "category",
+ label: "Foundations",
+ items: [
+ "learn/foundations/what-makes-fluree-different",
+ "learn/foundations/how-fluree-stores-data",
+ "learn/foundations/understanding-graph-relationships",
+ "learn/foundations/from-tables-to-graphs",
+ "learn/foundations/json-ld",
+ "learn/foundations/understanding-context",
],
},
{
- Guides: [
- "learn/guides/working-with-context",
- "learn/guides/working-with-ontologies",
+ type: "category",
+ label: "Working with Data",
+ items: [
+ "learn/working-with-data/inserting-data",
+ "learn/working-with-data/updating-and-deleting-data",
+ "learn/working-with-data/querying-basics",
+ "learn/working-with-data/advanced-queries",
+ "learn/working-with-data/context-patterns",
+ "learn/working-with-data/time-travel-and-history",
],
},
{
- Foundations: [
- "learn/foundations/json-ld",
- "learn/foundations/querying",
- "learn/foundations/data-access-control",
- "learn/foundations/verifiable-data",
- "learn/foundations/semantic-vocabularies",
- "learn/foundations/architecture-overview",
- "learn/foundations/from-tables-to-graphs",
+ type: "category",
+ label: "Modeling Your Data",
+ items: [
+ "learn/modeling/classes-and-properties",
+ "learn/modeling/building-ontologies",
+ "learn/modeling/working-with-ontologies",
+ "learn/modeling/shacl-validation",
+ "learn/modeling/semantic-vocabularies",
+ "learn/modeling/introduction-to-reasoning",
+ ],
+ },
+ {
+ type: "category",
+ label: "Security & Access Control",
+ items: [
+ "learn/security/data-access-control-concepts",
+ "learn/security/writing-policies",
+ "learn/security/verifiable-data",
+ ],
+ },
+ {
+ type: "category",
+ label: "Advanced Topics",
+ items: [
+ "learn/advanced/calculated-transactions",
+ "learn/advanced/language-tags",
+ "learn/advanced/architecture-overview",
+ "learn/advanced/collaborative-data",
],
},
],
examples: [
+ "examples/home",
+ {
+ type: "category",
+ label: "Domain Examples",
+ items: [
+ {
+ type: "category",
+ label: "E-Commerce Catalog",
+ items: [
+ "examples/datasets/ecommerce/introduction",
+ "examples/datasets/ecommerce/querying",
+ "examples/datasets/ecommerce/transactions",
+ ],
+ },
+ {
+ type: "category",
+ label: "Org Chart",
+ items: [
+ "examples/datasets/org-chart/introduction",
+ "examples/datasets/org-chart/querying",
+ "examples/datasets/org-chart/reasoning",
+ ],
+ },
+ {
+ type: "category",
+ label: "Knowledge Base",
+ items: [
+ "examples/datasets/knowledge-base/introduction",
+ "examples/datasets/knowledge-base/querying",
+ "examples/datasets/knowledge-base/history",
+ ],
+ },
+ {
+ type: "category",
+ label: "Healthcare Records",
+ items: [
+ "examples/datasets/healthcare/introduction",
+ "examples/datasets/healthcare/querying",
+ "examples/datasets/healthcare/policies",
+ ],
+ },
+ {
+ type: "category",
+ label: "Supply Chain",
+ items: [
+ "examples/datasets/supply-chain/introduction",
+ "examples/datasets/supply-chain/querying",
+ "examples/datasets/supply-chain/history",
+ ],
+ },
+ ],
+ },
+ {
+ type: "category",
+ label: "Feature Showcases",
+ items: [
+ {
+ type: "category",
+ label: "Policy & Access Control",
+ items: [
+ "examples/datasets/access-control/introduction",
+ "examples/datasets/access-control/defining-policies",
+ "examples/datasets/access-control/testing-policies",
+ ],
+ },
+ {
+ type: "category",
+ label: "Reasoning & Inference",
+ items: [
+ "examples/datasets/reasoning/introduction",
+ "examples/datasets/reasoning/rules",
+ "examples/datasets/reasoning/querying-inferred",
+ ],
+ },
+ {
+ type: "category",
+ label: "Full-Text Search",
+ items: [
+ "examples/datasets/full-text-search/introduction",
+ "examples/datasets/full-text-search/search-queries",
+ ],
+ },
+ {
+ type: "category",
+ label: "SHACL Validation",
+ items: [
+ "examples/datasets/shacl-validation/introduction",
+ "examples/datasets/shacl-validation/defining-shapes",
+ "examples/datasets/shacl-validation/validation-errors",
+ ],
+ },
+ {
+ type: "category",
+ label: "Time-Travel & Audit",
+ items: [
+ "examples/datasets/time-travel/introduction",
+ "examples/datasets/time-travel/history-queries",
+ ],
+ },
+ ],
+ },
{
- "Sharing Secure, Trusted Data": [
+ type: "category",
+ label: "Integration Guides",
+ items: [
+ {
+ type: "category",
+ label: "SQL Migration",
+ items: [
+ "examples/datasets/sql-migration/introduction",
+ "examples/datasets/sql-migration/schema-mapping",
+ "examples/datasets/sql-migration/query-comparison",
+ ],
+ },
+ {
+ type: "category",
+ label: "REST API Patterns",
+ items: [
+ "examples/datasets/rest-api/introduction",
+ "examples/datasets/rest-api/crud-operations",
+ "examples/datasets/rest-api/advanced-patterns",
+ ],
+ },
+ ],
+ },
+ {
+ type: "category",
+ label: "Academic Credentials",
+ items: [
"examples/datasets/academic-credentials/introduction",
"examples/datasets/academic-credentials/querying",
],
},
],
reference: [
- "reference/http-api",
- "reference/cloud-http-api",
- "reference/fluree-server-config",
- "reference/flureeql-query-syntax",
- "reference/history-syntax",
- "reference/transaction-syntax",
- "reference/cookbook",
+ {
+ type: "category",
+ label: "HTTP API",
+ items: [
+ "reference/http-api/index",
+ "reference/http-api/ledger-operations",
+ "reference/http-api/transactions",
+ "reference/http-api/queries",
+ "reference/http-api/real-time",
+ "reference/http-api/remote",
+ ],
+ },
+ {
+ type: "category",
+ label: "Querying",
+ items: [
+ {
+ type: "category",
+ label: "FlureeQL",
+ items: [
+ "reference/querying/index",
+ "reference/querying/select-clauses",
+ "reference/querying/where-clauses",
+ "reference/querying/aggregations",
+ "reference/querying/modifiers",
+ "reference/querying/advanced-features",
+ ],
+ },
+ "reference/full-text-search",
+ "reference/history-syntax",
+ ],
+ },
+ {
+ type: "category",
+ label: "SPARQL",
+ items: [
+ "reference/sparql-syntax",
+ ],
+ },
+ {
+ type: "category",
+ label: "Transactions",
+ items: [
+ "reference/transaction-syntax",
+ "reference/data-types",
+ ],
+ },
+ {
+ type: "category",
+ label: "Schema & Validation",
+ items: [
+ "reference/shacl-validation",
+ "reference/reasoning",
+ ],
+ },
+ {
+ type: "category",
+ label: "Security",
+ items: [
+ "reference/authentication",
+ "reference/policy-syntax",
+ ],
+ },
+ {
+ type: "category",
+ label: "Configuration",
+ items: [
+ "reference/fluree-server-config",
+ ],
+ },
+ {
+ type: "category",
+ label: "Cookbook",
+ items: [
+ "reference/cookbook/index",
+ "reference/cookbook/examples",
+ ],
+ },
"reference/error-codes",
- "reference/policy-syntax",
- "reference/data-types",
],
cloud: [
"cloud/welcome",
@@ -584,6 +816,13 @@ module.exports = {
},
],
},
+ {
+ type: "category",
+ label: "API Reference",
+ items: [
+ "reference/cloud-http-api",
+ ],
+ },
]
};
\ No newline at end of file
diff --git a/src/pages/index.jsx b/src/pages/index.jsx
index b9f5f26..7eecc01 100644
--- a/src/pages/index.jsx
+++ b/src/pages/index.jsx
@@ -33,7 +33,7 @@ const HomePage = () => {
Get started
@@ -52,61 +52,53 @@ const HomePage = () => {
-
Learn
+
Getting Started
- What sets Fluree apart and how it can help you
+ Create a ledger, insert data, and run queries in 5 minutes
-
- Learn the essentials of using Fluree and develop an accurate
- mental model of the system
+ Docker, JAR deployment, and configuration options
- {/*
*/}
- Get into the details of working with Open Standards and Fluree
+ Understand ledgers, entities, JSON-LD, and time travel
- {/*
+
+
+
+
+
+
+ Concepts, guides, and advanced topics for mastering Fluree
-
*/}
- Deep dives into slices of Fluree functionality
+ Deep dives into JSON-LD, querying, policies, and architecture
{/*