diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,9 @@
 # Changelog for hs-opentelemetry-instrumentation-hspec
 
+## 1.0.0.0 - 2026-05-29
+
+- Promoted to 1.0.0.0 for the hs-opentelemetry 1.0 release.
+
 ## 0.0.1.3
 
 - Relax `hs-opentelemetry-api` bounds to support 0.3.x
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright Ian Duncan (c) 2021
+Copyright Ian Duncan (c) 2021-2026
 
 All rights reserved.
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,9 +1,23 @@
 # hs-opentelemetry-instrumentation-hspec
 
-Instrumentation for Hspec for OpenTelemetry. This creates a span for each test
-case inside a test suite.
+[![hs-opentelemetry-instrumentation-hspec](https://img.shields.io/hackage/v/hs-opentelemetry-instrumentation-hspec?style=flat-square&logo=haskell&label=hs-opentelemetry-instrumentation-hspec&labelColor=5D4F85)](https://hackage.haskell.org/package/hs-opentelemetry-instrumentation-hspec)
 
+OpenTelemetry instrumentation for the [Hspec] test framework; it creates one span per test case inside the instrumented `Spec` tree.
+
+[Hspec]: https://hackage.haskell.org/package/hspec
+
 ## Usage
 
-See the example in [examples/hspec](../../examples/hspec) for an instrumented
-test suite.
+```haskell
+do
+  provider <- getGlobalTracerProvider
+  let tracer = OpenTelemetry.Trace.makeTracer provider "my-test-suite" OpenTelemetry.Trace.tracerOptions
+  context <- OpenTelemetry.Context.ThreadLocal.getContext
+
+  hspec $ instrumentSpec tracer context $ do
+    describe "Spec" do
+      it "is instrumented with OpenTelemetry" do
+        True `shouldBe` True
+```
+
+See [examples/hspec](../../examples/hspec) for an instrumented test suite.
diff --git a/hs-opentelemetry-instrumentation-hspec.cabal b/hs-opentelemetry-instrumentation-hspec.cabal
--- a/hs-opentelemetry-instrumentation-hspec.cabal
+++ b/hs-opentelemetry-instrumentation-hspec.cabal
@@ -1,20 +1,22 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.37.0.
+-- This file has been generated from package.yaml by hpack version 0.38.3.
 --
 -- see: https://github.com/sol/hpack
 
-name:               hs-opentelemetry-instrumentation-hspec
-version:            0.0.1.3
-description:        Please see the README on GitHub at <https://github.com/iand675/hs-opentelemetry/tree/main/instrumentation/hspec#readme>
-homepage:           https://github.com/iand675/hs-opentelemetry#readme
-bug-reports:        https://github.com/iand675/hs-opentelemetry/issues
-author:             Ian Duncan, Jade Lovelace
-maintainer:         ian@iankduncan.com
-copyright:          2024 Ian Duncan, Mercury Technologies
-license:            BSD3
-license-file:       LICENSE
-build-type:         Simple
+name:           hs-opentelemetry-instrumentation-hspec
+version:        1.0.0.0
+synopsis:       OpenTelemetry instrumentation for Hspec test suites
+description:    Please see the README on GitHub at <https://github.com/iand675/hs-opentelemetry/tree/main/instrumentation/hspec#readme>
+category:       OpenTelemetry, Testing
+homepage:       https://github.com/iand675/hs-opentelemetry#readme
+bug-reports:    https://github.com/iand675/hs-opentelemetry/issues
+author:         Ian Duncan, Jade Lovelace
+maintainer:     ian@iankduncan.com
+copyright:      2024 Ian Duncan, Mercury Technologies
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
 extra-source-files:
     README.md
     ChangeLog.md
@@ -32,8 +34,27 @@
       src
   build-depends:
       base >=4.7 && <5
-    , hs-opentelemetry-api ==0.3.*
+    , hs-opentelemetry-api ==1.0.*
     , hspec-core >=2.9.4
     , mtl
+    , text
+  default-language: Haskell2010
+
+test-suite hs-opentelemetry-hspec-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Paths_hs_opentelemetry_instrumentation_hspec
+  hs-source-dirs:
+      test
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , hs-opentelemetry-api ==1.0.*
+    , hs-opentelemetry-exporter-in-memory ==1.0.*
+    , hs-opentelemetry-instrumentation-hspec ==1.0.*
+    , hs-opentelemetry-sdk ==1.0.*
+    , hspec
+    , hspec-core
     , text
   default-language: Haskell2010
diff --git a/src/OpenTelemetry/Instrumentation/Hspec.hs b/src/OpenTelemetry/Instrumentation/Hspec.hs
--- a/src/OpenTelemetry/Instrumentation/Hspec.hs
+++ b/src/OpenTelemetry/Instrumentation/Hspec.hs
@@ -1,24 +1,56 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TemplateHaskell #-}
 
--- | Instrumentation for Hspec test suites
+{- |
+Module      : OpenTelemetry.Instrumentation.Hspec
+Copyright   : (c) Ian Duncan, 2021-2026
+License     : BSD-3
+Description : Trace Hspec test suites as spans
+Stability   : experimental
+
+= Overview
+
+Adds tracing around Hspec examples. Useful for CI observability: see which
+tests ran, how long they took, and which ones failed.
+
+'wrapSpec' uses the global tracer provider; compose it with
+'OpenTelemetry.Trace.withTracerProvider' so the provider is initialized before
+@hspec@ runs. For full control over tracer and parent context, use
+'instrumentSpec' instead.
+
+= Quick example
+
+@
+import Test.Hspec
+import OpenTelemetry.Instrumentation.Hspec (wrapSpec)
+import OpenTelemetry.Trace (withTracerProvider)
+
+main :: IO ()
+main = withTracerProvider $ \_ -> do
+  runSpec <- wrapSpec
+  hspec $ runSpec mySpec
+@
+
+'OpenTelemetry.Trace.withTracerProvider' lives in @hs-opentelemetry-sdk@.
+
+= Nested structure
+
+'instrumentSpec' adds spans for @describe@ nesting; 'wrapSpec' produces a flat
+span per @it@ (see its Haddock for rationale).
+-}
 module OpenTelemetry.Instrumentation.Hspec (
   wrapSpec,
   wrapExampleInSpan,
   instrumentSpec,
 ) where
 
-import Control.Monad (void)
 import Control.Monad.IO.Class
-import Control.Monad.Reader
 import qualified Data.List as List
-import Data.Text (Text)
 import qualified Data.Text as T
-import OpenTelemetry.Attributes (Attributes)
 import OpenTelemetry.Context
-import OpenTelemetry.Context.ThreadLocal (adjustContext, attachContext, getContext)
+import OpenTelemetry.Context.ThreadLocal (attachContext, getContext)
 import OpenTelemetry.Trace.Core
-import Test.Hspec.Core.Spec (ActionWith, Item (..), Spec, SpecWith, Tree (..), mapSpecForest, mapSpecItem_)
+import Test.Hspec.Core.Spec (Item (..), SpecWith, Tree (..), mapSpecForest, mapSpecItem_)
 
 
 {- | Creates a wrapper function that you can pass a spec into.
@@ -35,9 +67,9 @@
   let tracer = makeTracer tp $detectInstrumentationLibrary tracerOptions
   context <- getContext
 
-  -- FIXME: this kind of just dumps everything flat into one span per `it`. We
-  -- could possibly do better, e.g. finding the `describe`s and making them into
-  -- spans but I am not sure how that would be achieved.
+  -- Span structure is flat: one span per @it@ item. Nesting spans under
+  -- @describe@ blocks would require access to hspec's internal spec tree
+  -- before execution, which isn't exposed by the hspec public API.
   pure $ \spec -> mapSpecItem_ (wrapExampleInSpan tracer context) spec
 
 
@@ -50,7 +82,7 @@
     { itemExample = \params aroundAction pcb -> do
         let aroundAction' a = do
               -- we need to reattach the context, since we are on a forked thread
-              void $ attachContext traceContext
+              _ <- attachContext traceContext
               inSpan tp (T.pack req) defaultSpanArguments (aroundAction a)
 
         ex params aroundAction' pcb
@@ -75,7 +107,7 @@
             { itemExample = \params aroundAction pcb -> do
                 let aroundAction' a = do
                       -- we need to reattach the context, since we are on a forked thread
-                      void $ attachContext traceContext
+                      _ <- attachContext traceContext
                       addSpans spans $ inSpan tracer (T.pack (itemRequirement item)) defaultSpanArguments (aroundAction a)
 
                 itemExample item params aroundAction' pcb
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,58 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import Data.IORef
+import OpenTelemetry.Context.ThreadLocal (getContext)
+import OpenTelemetry.Exporter.InMemory.Span (inMemoryListExporter)
+import OpenTelemetry.Instrumentation.Hspec (instrumentSpec)
+import OpenTelemetry.Trace.Core
+import Test.Hspec
+import Test.Hspec.Core.Runner (hspecResult)
+
+
+main :: IO ()
+main = hspec spec
+
+
+spec :: Spec
+spec = describe "Hspec instrumentation" $ do
+  it "instrumentSpec creates spans for each test item" $ do
+    (processor, ref) <- inMemoryListExporter
+    tp <- createTracerProvider [processor] emptyTracerProviderOptions
+    setGlobalTracerProvider tp
+    let tracer = makeTracer tp "test" tracerOptions
+    ctx <- getContext
+
+    let innerSpec = instrumentSpec tracer ctx $ do
+          it "alpha" $ True `shouldBe` True
+          it "beta" $ True `shouldBe` True
+
+    _summary <- hspecResult innerSpec
+
+    _ <- shutdownTracerProvider tp Nothing
+    spans <- readIORef ref
+    names <- traverse (\s -> hotName <$> readIORef (spanHot s)) spans
+    names `shouldContain` ["alpha"]
+    names `shouldContain` ["beta"]
+
+  it "instrumentSpec nests describe groups as spans" $ do
+    (processor, ref) <- inMemoryListExporter
+    tp <- createTracerProvider [processor] emptyTracerProviderOptions
+    setGlobalTracerProvider tp
+    let tracer = makeTracer tp "test-nested" tracerOptions
+    ctx <- getContext
+
+    let innerSpec =
+          instrumentSpec tracer ctx $
+            describe "outer group" $
+              it "nested test" $
+                True `shouldBe` True
+
+    _summary <- hspecResult innerSpec
+
+    _ <- shutdownTracerProvider tp Nothing
+    spans <- readIORef ref
+    names <- traverse (\s -> hotName <$> readIORef (spanHot s)) spans
+    names `shouldContain` ["nested test"]
+    names `shouldContain` ["outer group"]
