diff --git a/CHANGELOG.md b/CHANGELOG.md
index e32cc6bf..87ccad00 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
 ## Unreleased
 
 ### Added
+- Wasm artifacts
 
 ### Changed
 
@@ -11,6 +12,7 @@
 ### Removed
 
 ### Fixed
+- Partially fixed a bug with `MutableMeta` observable wrappers.
 
 ### Security
 
diff --git a/build.gradle.kts b/build.gradle.kts
index 4b5fe48c..edeae557 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,7 +8,7 @@ plugins {
 
 allprojects {
     group = "space.kscience"
-    version = "0.7.0"
+    version = "0.7.1"
 }
 
 subprojects {
@@ -31,7 +31,7 @@ ksciencePublish {
         useSPCTeam()
     }
     repository("spc","https://maven.sciprog.center/kscience")
-    sonatype()
+    sonatype("https://oss.sonatype.org")
 }
 
 apiValidation {
diff --git a/dataforge-context/build.gradle.kts b/dataforge-context/build.gradle.kts
index be9036d0..b59abed0 100644
--- a/dataforge-context/build.gradle.kts
+++ b/dataforge-context/build.gradle.kts
@@ -8,12 +8,14 @@ kscience {
     jvm()
     js()
     native()
+    wasm()
     useCoroutines()
     useSerialization()
-    dependencies {
+    commonMain {
         api(project(":dataforge-meta"))
+        api(spclibs.atomicfu)
     }
-    dependencies(jvmMain){
+    jvmMain{
         api(kotlin("reflect"))
         api("org.slf4j:slf4j-api:1.7.30")
     }
diff --git a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Plugin.kt b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Plugin.kt
index 152f5a76..6c5648a6 100644
--- a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Plugin.kt
+++ b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Plugin.kt
@@ -3,7 +3,7 @@ package space.kscience.dataforge.context
 import space.kscience.dataforge.context.Plugin.Companion.TARGET
 import space.kscience.dataforge.meta.Meta
 import space.kscience.dataforge.meta.MetaRepr
-import space.kscience.dataforge.misc.DfId
+import space.kscience.dataforge.misc.DfType
 import space.kscience.dataforge.misc.Named
 import space.kscience.dataforge.names.Name
 import space.kscience.dataforge.names.parseAsName
@@ -18,7 +18,7 @@ import space.kscience.dataforge.provider.Provider
  *
  * create - configure - attach - detach - destroy
  */
-@DfId(TARGET)
+@DfType(TARGET)
 public interface Plugin : Named, ContextAware, Provider, MetaRepr {
 
     /**
diff --git a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/PluginFactory.kt b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/PluginFactory.kt
index 0273d327..9cc67168 100644
--- a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/PluginFactory.kt
+++ b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/PluginFactory.kt
@@ -1,9 +1,9 @@
 package space.kscience.dataforge.context
 
 import space.kscience.dataforge.meta.Meta
-import space.kscience.dataforge.misc.DfId
+import space.kscience.dataforge.misc.DfType
 
-@DfId(PluginFactory.TYPE)
+@DfType(PluginFactory.TYPE)
 public interface PluginFactory<T : Plugin> : Factory<T> {
     public val tag: PluginTag
 
diff --git a/dataforge-context/src/jvmMain/kotlin/space/kscience/dataforge/provider/dfType.kt b/dataforge-context/src/jvmMain/kotlin/space/kscience/dataforge/provider/dfType.kt
index ab34ea50..04e681da 100644
--- a/dataforge-context/src/jvmMain/kotlin/space/kscience/dataforge/provider/dfType.kt
+++ b/dataforge-context/src/jvmMain/kotlin/space/kscience/dataforge/provider/dfType.kt
@@ -4,7 +4,7 @@ import space.kscience.dataforge.context.Context
 import space.kscience.dataforge.context.PluginBuilder
 import space.kscience.dataforge.context.gather
 import space.kscience.dataforge.misc.DFExperimental
-import space.kscience.dataforge.misc.DfId
+import space.kscience.dataforge.misc.DfType
 import space.kscience.dataforge.misc.Named
 import space.kscience.dataforge.names.Name
 import kotlin.reflect.KClass
@@ -13,10 +13,10 @@ import kotlin.reflect.full.findAnnotation
 
 @DFExperimental
 public val KClass<*>.dfId: String
-    get() = findAnnotation<DfId>()?.id ?: simpleName ?: ""
+    get() = findAnnotation<DfType>()?.id ?: simpleName ?: ""
 
 /**
- * Provide an object with given name inferring target from its type using [DfId] annotation
+ * Provide an object with given name inferring target from its type using [DfType] annotation
  */
 @DFExperimental
 public inline fun <reified T : Any> Provider.provideByType(name: String): T? {
diff --git a/dataforge-context/src/wasmJsMain/kotlin/space/kscience/dataforge/context/loggingWasm.kt b/dataforge-context/src/wasmJsMain/kotlin/space/kscience/dataforge/context/loggingWasm.kt
new file mode 100644
index 00000000..740957b4
--- /dev/null
+++ b/dataforge-context/src/wasmJsMain/kotlin/space/kscience/dataforge/context/loggingWasm.kt
@@ -0,0 +1,3 @@
+package space.kscience.dataforge.context
+
+internal actual fun getGlobalLoggerFactory(): PluginFactory<out LogManager> = DefaultLogManager
\ No newline at end of file
diff --git a/dataforge-data/build.gradle.kts b/dataforge-data/build.gradle.kts
index 9f96604a..ea542290 100644
--- a/dataforge-data/build.gradle.kts
+++ b/dataforge-data/build.gradle.kts
@@ -6,9 +6,11 @@ kscience{
     jvm()
     js()
     native()
+    wasm()
     useCoroutines()
     dependencies {
-        api(project(":dataforge-meta"))
+        api(spclibs.atomicfu)
+        api(projects.dataforgeMeta)
         api(kotlin("reflect"))
     }
 }
diff --git a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/MapAction.kt b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/MapAction.kt
index 883b3928..7b2c94f5 100644
--- a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/MapAction.kt
+++ b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/MapAction.kt
@@ -98,8 +98,7 @@ internal class MapAction<in T : Any, R : Any>(
  * A one-to-one mapping action
  */
 @DFExperimental
-@Suppress("FunctionName")
-public inline fun <T : Any, reified R : Any> Action.Companion.map(
+public inline fun <T : Any, reified R : Any> Action.Companion.mapping(
     noinline builder: MapActionBuilder<T, R>.() -> Unit,
 ): Action<T, R> = MapAction(typeOf<R>(), builder)
 
diff --git a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/ReduceAction.kt b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/ReduceAction.kt
index fe823bd7..a74cfad9 100644
--- a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/ReduceAction.kt
+++ b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/ReduceAction.kt
@@ -112,6 +112,6 @@ internal class ReduceAction<T : Any, R : Any>(
  * A one-to-one mapping action
  */
 @DFExperimental
-public inline fun <reified T : Any, reified R : Any> Action.Companion.reduce(
+public inline fun <reified T : Any, reified R : Any> Action.Companion.reducing(
     noinline builder: ReduceGroupBuilder<T, R>.() -> Unit,
 ): Action<T, R> = ReduceAction(typeOf<R>(), builder)
diff --git a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/SplitAction.kt b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/SplitAction.kt
index 24745929..0ecde319 100644
--- a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/SplitAction.kt
+++ b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/SplitAction.kt
@@ -87,6 +87,6 @@ internal class SplitAction<T : Any, R : Any>(
  * Action that splits each incoming element into a number of fragments defined in builder
  */
 @DFExperimental
-public inline fun <T : Any, reified R : Any> Action.Companion.split(
+public inline fun <T : Any, reified R : Any> Action.Companion.splitting(
     noinline builder: SplitBuilder<T, R>.() -> Unit,
 ): Action<T, R> = SplitAction(typeOf<R>(), builder)
\ No newline at end of file
diff --git a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/Data.kt b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/Data.kt
index 984582e5..4d883795 100644
--- a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/Data.kt
+++ b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/Data.kt
@@ -5,7 +5,7 @@ import space.kscience.dataforge.meta.Meta
 import space.kscience.dataforge.meta.MetaRepr
 import space.kscience.dataforge.meta.isEmpty
 import space.kscience.dataforge.misc.DFInternal
-import space.kscience.dataforge.misc.DfId
+import space.kscience.dataforge.misc.DfType
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.reflect.KType
@@ -14,7 +14,7 @@ import kotlin.reflect.typeOf
 /**
  * A data element characterized by its meta
  */
-@DfId(Data.TYPE)
+@DfType(Data.TYPE)
 public interface Data<out T> : Goal<T>, MetaRepr {
     /**
      * Type marker for the data. The type is known before the calculation takes place so it could be checked.
diff --git a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataTree.kt b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataTree.kt
index bafcbea2..b9273c07 100644
--- a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataTree.kt
+++ b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataTree.kt
@@ -2,7 +2,7 @@ package space.kscience.dataforge.data
 
 import space.kscience.dataforge.meta.Meta
 import space.kscience.dataforge.misc.DFInternal
-import space.kscience.dataforge.misc.DfId
+import space.kscience.dataforge.misc.DfType
 import space.kscience.dataforge.names.*
 import kotlin.collections.component1
 import kotlin.collections.component2
@@ -31,7 +31,7 @@ public val <T : Any> DataTreeItem<T>.type: KType
 /**
  * A tree-like [DataSet] grouped into the node. All data inside the node must inherit its type
  */
-@DfId(DataTree.TYPE)
+@DfType(DataTree.TYPE)
 public interface DataTree<out T : Any> : DataSet<T> {
 
     /**
diff --git a/dataforge-data/src/jvmMain/kotlin/space/kscience/dataforge/data/actionInContext.kt b/dataforge-data/src/jvmMain/kotlin/space/kscience/dataforge/data/actionInContext.kt
deleted file mode 100644
index 33731a95..00000000
--- a/dataforge-data/src/jvmMain/kotlin/space/kscience/dataforge/data/actionInContext.kt
+++ /dev/null
@@ -1,2 +0,0 @@
-package space.kscience.dataforge.data
-
diff --git a/dataforge-data/src/jvmTest/kotlin/space/kscience/dataforge/data/ActionsTest.kt b/dataforge-data/src/jvmTest/kotlin/space/kscience/dataforge/data/ActionsTest.kt
index 3987cd19..b24c4f27 100644
--- a/dataforge-data/src/jvmTest/kotlin/space/kscience/dataforge/data/ActionsTest.kt
+++ b/dataforge-data/src/jvmTest/kotlin/space/kscience/dataforge/data/ActionsTest.kt
@@ -6,7 +6,7 @@ import kotlinx.coroutines.test.runTest
 import org.junit.jupiter.api.Test
 import space.kscience.dataforge.actions.Action
 import space.kscience.dataforge.actions.invoke
-import space.kscience.dataforge.actions.map
+import space.kscience.dataforge.actions.mapping
 import space.kscience.dataforge.misc.DFExperimental
 import kotlin.test.assertEquals
 
@@ -20,7 +20,7 @@ internal class ActionsTest {
             }
         }
 
-        val plusOne = Action.map<Int, Int> {
+        val plusOne = Action.mapping<Int, Int> {
             result { it + 1 }
         }
         val result = plusOne(data)
@@ -31,7 +31,7 @@ internal class ActionsTest {
     fun testDynamicMapAction() = runTest {
         val data: DataSourceBuilder<Int> = DataSource()
 
-        val plusOne = Action.map<Int, Int> {
+        val plusOne = Action.mapping<Int, Int> {
             result { it + 1 }
         }
 
diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts
index 6d3c888c..f7197197 100644
--- a/dataforge-io/build.gradle.kts
+++ b/dataforge-io/build.gradle.kts
@@ -4,12 +4,13 @@ plugins {
 
 description = "IO module"
 
-val ioVersion = "0.2.1"
+val ioVersion = "0.3.0"
 
 kscience {
     jvm()
     js()
     native()
+    wasm()
     useSerialization()
     useSerialization(sourceSet = space.kscience.gradle.DependencySourceSet.TEST) {
         cbor()
diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/EnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/EnvelopeFormat.kt
index 0df5ab27..0e998760 100644
--- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/EnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/EnvelopeFormat.kt
@@ -4,7 +4,7 @@ import kotlinx.io.Source
 import space.kscience.dataforge.context.Context
 import space.kscience.dataforge.io.EnvelopeFormatFactory.Companion.ENVELOPE_FORMAT_TYPE
 import space.kscience.dataforge.meta.Meta
-import space.kscience.dataforge.misc.DfId
+import space.kscience.dataforge.misc.DfType
 import space.kscience.dataforge.names.Name
 import space.kscience.dataforge.names.asName
 import kotlin.reflect.KType
@@ -17,7 +17,7 @@ public interface EnvelopeFormat : IOFormat<Envelope> {
 
 public fun EnvelopeFormat.read(input: Source): Envelope = readFrom(input)
 
-@DfId(ENVELOPE_FORMAT_TYPE)
+@DfType(ENVELOPE_FORMAT_TYPE)
 public interface EnvelopeFormatFactory : IOFormatFactory<Envelope>, EnvelopeFormat {
     override val type: KType get() = typeOf<Envelope>()
 
diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOFormat.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOFormat.kt
index ffcadf1a..390a8bf4 100644
--- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOFormat.kt
@@ -7,7 +7,7 @@ import space.kscience.dataforge.context.Context
 import space.kscience.dataforge.context.Factory
 import space.kscience.dataforge.io.IOFormatFactory.Companion.IO_FORMAT_TYPE
 import space.kscience.dataforge.meta.Meta
-import space.kscience.dataforge.misc.DfId
+import space.kscience.dataforge.misc.DfType
 import space.kscience.dataforge.misc.Named
 import space.kscience.dataforge.names.Name
 import space.kscience.dataforge.names.asName
@@ -72,7 +72,7 @@ public fun <T : Any> Sink.writeWith(format: IOWriter<T>, obj: T): Unit =
     format.writeTo(this, obj)
 
 
-@DfId(IO_FORMAT_TYPE)
+@DfType(IO_FORMAT_TYPE)
 public interface IOFormatFactory<T : Any> : Factory<IOFormat<T>>, Named {
     /**
      * Explicit type for dynamic type checks
diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/MetaFormat.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/MetaFormat.kt
index cadf87ca..f864dd2f 100644
--- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/MetaFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/MetaFormat.kt
@@ -9,7 +9,7 @@ import space.kscience.dataforge.context.Global
 import space.kscience.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE
 import space.kscience.dataforge.meta.Meta
 import space.kscience.dataforge.meta.descriptors.MetaDescriptor
-import space.kscience.dataforge.misc.DfId
+import space.kscience.dataforge.misc.DfType
 import space.kscience.dataforge.names.Name
 import space.kscience.dataforge.names.asName
 import space.kscience.dataforge.names.plus
@@ -38,7 +38,7 @@ public interface MetaFormat : IOFormat<Meta> {
     public fun readMeta(source: Source, descriptor: MetaDescriptor? = null): Meta
 }
 
-@DfId(META_FORMAT_TYPE)
+@DfType(META_FORMAT_TYPE)
 public interface MetaFormatFactory : IOFormatFactory<Meta>, MetaFormat {
     public val shortName: String
 
diff --git a/dataforge-meta/api/dataforge-meta.api b/dataforge-meta/api/dataforge-meta.api
index 7daa7540..1700ca7d 100644
--- a/dataforge-meta/api/dataforge-meta.api
+++ b/dataforge-meta/api/dataforge-meta.api
@@ -724,6 +724,7 @@ public final class space/kscience/dataforge/meta/descriptors/MetaDescriptorBuild
 	public final fun getAttributes ()Lspace/kscience/dataforge/meta/MutableMeta;
 	public final fun getChildren ()Ljava/util/Map;
 	public final fun getDefault ()Lspace/kscience/dataforge/meta/Value;
+	public final fun getDescription ()Ljava/lang/String;
 	public final fun getIndexKey ()Ljava/lang/String;
 	public final fun getInfo ()Ljava/lang/String;
 	public final fun getMultiple ()Z
@@ -737,6 +738,7 @@ public final class space/kscience/dataforge/meta/descriptors/MetaDescriptorBuild
 	public final fun setAttributes (Lspace/kscience/dataforge/meta/MutableMeta;)V
 	public final fun setChildren (Ljava/util/Map;)V
 	public final fun setDefault (Lspace/kscience/dataforge/meta/Value;)V
+	public final fun setDescription (Ljava/lang/String;)V
 	public final fun setIndexKey (Ljava/lang/String;)V
 	public final fun setInfo (Ljava/lang/String;)V
 	public final fun setMultiple (Z)V
@@ -903,7 +905,7 @@ public abstract interface annotation class space/kscience/dataforge/misc/DFExper
 public abstract interface annotation class space/kscience/dataforge/misc/DFInternal : java/lang/annotation/Annotation {
 }
 
-public abstract interface annotation class space/kscience/dataforge/misc/DfId : java/lang/annotation/Annotation {
+public abstract interface annotation class space/kscience/dataforge/misc/DfType : java/lang/annotation/Annotation {
 	public abstract fun id ()Ljava/lang/String;
 }
 
@@ -944,6 +946,7 @@ public final class space/kscience/dataforge/names/NameKt {
 	public static final fun asName (Lspace/kscience/dataforge/names/NameToken;)Lspace/kscience/dataforge/names/Name;
 	public static final fun cutFirst (Lspace/kscience/dataforge/names/Name;)Lspace/kscience/dataforge/names/Name;
 	public static final fun cutLast (Lspace/kscience/dataforge/names/Name;)Lspace/kscience/dataforge/names/Name;
+	public static final fun endsWith (Lspace/kscience/dataforge/names/Name;Ljava/lang/String;)Z
 	public static final fun endsWith (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;)Z
 	public static final fun endsWith (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/NameToken;)Z
 	public static final fun first (Lspace/kscience/dataforge/names/Name;)Lspace/kscience/dataforge/names/NameToken;
@@ -966,6 +969,7 @@ public final class space/kscience/dataforge/names/NameKt {
 	public static final fun removeHeadOrNull (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;)Lspace/kscience/dataforge/names/Name;
 	public static final fun replaceLast (Lspace/kscience/dataforge/names/Name;Lkotlin/jvm/functions/Function1;)Lspace/kscience/dataforge/names/Name;
 	public static final fun set (Ljava/util/Map;Ljava/lang/String;Ljava/lang/Object;)V
+	public static final fun startsWith (Lspace/kscience/dataforge/names/Name;Ljava/lang/String;)Z
 	public static final fun startsWith (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/Name;)Z
 	public static final fun startsWith (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/names/NameToken;)Z
 	public static final fun withIndex (Lspace/kscience/dataforge/names/Name;Ljava/lang/String;)Lspace/kscience/dataforge/names/Name;
diff --git a/dataforge-meta/build.gradle.kts b/dataforge-meta/build.gradle.kts
index 51b07113..d150ef98 100644
--- a/dataforge-meta/build.gradle.kts
+++ b/dataforge-meta/build.gradle.kts
@@ -6,6 +6,7 @@ kscience {
     jvm()
     js()
     native()
+    wasm()
     useSerialization{
         json()
     }
diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Meta.kt
index 420625ca..979c8782 100644
--- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Meta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Meta.kt
@@ -2,7 +2,7 @@ package space.kscience.dataforge.meta
 
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.json.Json
-import space.kscience.dataforge.misc.DfId
+import space.kscience.dataforge.misc.DfType
 import space.kscience.dataforge.misc.unsafeCast
 import space.kscience.dataforge.names.*
 import kotlin.jvm.JvmName
@@ -31,7 +31,7 @@ public fun interface MetaProvider : ValueProvider {
  * TODO add documentation
  * Same name siblings are supported via elements with the same [Name] but different indices.
  */
-@DfId(Meta.TYPE)
+@DfType(Meta.TYPE)
 @Serializable(MetaSerializer::class)
 public interface Meta : MetaRepr, MetaProvider {
     public val value: Value?
@@ -248,7 +248,7 @@ public inline fun <reified E : Enum<E>> Meta?.enum(): E? = this?.value?.let {
     }
 }
 
-public val Meta.stringList: List<String>? get() = value?.list?.map { it.string }
+public val Meta?.stringList: List<String>? get() = this?.value?.list?.map { it.string }
 
 /**
  * Create a provider that uses given provider for default values if those are not found in this provider
diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMetaWrapper.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMetaWrapper.kt
index 76645d83..71e15aa9 100644
--- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMetaWrapper.kt
+++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMetaWrapper.kt
@@ -6,60 +6,71 @@ import space.kscience.dataforge.names.*
 
 /**
  * A class that takes [MutableMeta] provider and adds obsevability on top of that
+ *
+ * TODO rewrite to properly work with detached nodes
  */
 private class ObservableMetaWrapper(
     val root: MutableMeta,
-    val absoluteName: Name,
+    val nodeName: Name,
     val listeners: MutableSet<MetaListener>,
 ) : ObservableMutableMeta {
     override val items: Map<NameToken, ObservableMutableMeta>
         get() = root.items.keys.associateWith {
-            ObservableMetaWrapper(root, absoluteName + it, listeners)
+            ObservableMetaWrapper(root, nodeName + it, listeners)
         }
 
-    override fun get(name: Name): ObservableMutableMeta? =
-        root.get(name)?.let { ObservableMetaWrapper(root, this.absoluteName + name, listeners) }
+    override fun get(name: Name): ObservableMutableMeta? = if (root[nodeName + name] == null) {
+        null
+    } else {
+        ObservableMetaWrapper(root, nodeName + name, listeners)
+    }
 
     @ThreadSafe
     override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
         listeners.add(
-            MetaListener(Pair(owner, absoluteName)) { name ->
-                if (name.startsWith(absoluteName)) {
-                    (this[absoluteName] ?: Meta.EMPTY).callback(name.removeFirstOrNull(absoluteName)!!)
+            MetaListener(Pair(owner, nodeName)) { fullName ->
+                if (fullName.startsWith(nodeName)) {
+                    root[nodeName]?.callback(fullName.removeFirstOrNull(nodeName)!!)
                 }
             }
         )
     }
 
     override fun removeListener(owner: Any?) {
-        listeners.removeAll { it.owner === Pair(owner, absoluteName) }
+        listeners.removeAll { it.owner === Pair(owner, nodeName) }
     }
 
     override fun invalidate(name: Name) {
-        listeners.forEach { it.callback(this, name) }
+        listeners.forEach { it.callback(this, nodeName + name) }
     }
 
     override var value: Value?
-        get() = root.value
+        get() = root[nodeName]?.value
         set(value) {
-            root.value = value
+            root.getOrCreate(nodeName).value = value
             invalidate(Name.EMPTY)
         }
 
     override fun getOrCreate(name: Name): ObservableMutableMeta =
-        ObservableMetaWrapper(root, this.absoluteName + name, listeners)
+        ObservableMetaWrapper(root, nodeName + name, listeners)
 
-    override fun set(name: Name, node: Meta?) {
+    fun removeNode(name: Name): Meta? {
         val oldMeta = get(name)
         //don't forget to remove listener
         oldMeta?.removeListener(this)
-        root.set(absoluteName + name, node)
+
+        return oldMeta
+    }
+
+    override fun set(name: Name, node: Meta?) {
+        val oldMeta = removeNode(name)
+        root[nodeName + name] = node
         if (oldMeta != node) {
             invalidate(name)
         }
     }
 
-    override fun toMeta(): Meta = root[absoluteName]?.toMeta() ?: Meta.EMPTY
+    override fun toMeta(): Meta = root[nodeName]?.toMeta() ?: Meta.EMPTY
 
     override fun toString(): String = Meta.toString(this)
     override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptorBuilder.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptorBuilder.kt
index ae6c171a..95949d03 100644
--- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptorBuilder.kt
+++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptorBuilder.kt
@@ -9,7 +9,11 @@ import space.kscience.dataforge.names.length
 import kotlin.collections.set
 
 public class MetaDescriptorBuilder @PublishedApi internal constructor() {
-    public var info: String? = null
+    public var description: String? = null
+
+    @Deprecated("Replace by description", ReplaceWith("description"))
+    public var info: String? by ::description
+
     public var children: MutableMap<String, MetaDescriptorBuilder> = linkedMapOf()
     public var multiple: Boolean = false
     public var valueRestriction: ValueRestriction = ValueRestriction.NONE
@@ -87,7 +91,7 @@ public class MetaDescriptorBuilder @PublishedApi internal constructor() {
 
     @PublishedApi
     internal fun build(): MetaDescriptor = MetaDescriptor(
-        description = info,
+        description = description,
         children = children.mapValues { it.value.build() },
         multiple = multiple,
         valueRestriction = valueRestriction,
@@ -165,7 +169,7 @@ public inline fun <reified E : Enum<E>> MetaDescriptorBuilder.enum(
 }
 
 private fun MetaDescriptor.toBuilder(): MetaDescriptorBuilder = MetaDescriptorBuilder().apply {
-    info = this@toBuilder.description
+    description = this@toBuilder.description
     children = this@toBuilder.children.mapValuesTo(LinkedHashMap()) { it.value.toBuilder() }
     multiple = this@toBuilder.multiple
     valueRestriction = this@toBuilder.valueRestriction
diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/misc/DfId.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/misc/DfType.kt
similarity index 56%
rename from dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/misc/DfId.kt
rename to dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/misc/DfType.kt
index 5d485e23..11f548ae 100644
--- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/misc/DfId.kt
+++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/misc/DfType.kt
@@ -5,4 +5,7 @@ package space.kscience.dataforge.misc
  */
 @MustBeDocumented
 @Target(AnnotationTarget.CLASS)
-public annotation class DfId(val id: String)
+public annotation class DfType(val id: String)
+
+@Deprecated("Replace with DfType", replaceWith = ReplaceWith("DfType"))
+public typealias DfId = DfType
diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt
index 160ea3a1..1c9a9cf3 100644
--- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt
+++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt
@@ -216,9 +216,13 @@ public fun Name.endsWith(token: NameToken): Boolean = lastOrNull() == token
 public fun Name.startsWith(name: Name): Boolean =
     this.length >= name.length && (this == name || tokens.subList(0, name.length) == name.tokens)
 
+public fun Name.startsWith(name: String): Boolean = startsWith(name.parseAsName())
+
 public fun Name.endsWith(name: Name): Boolean =
     this.length >= name.length && (this == name || tokens.subList(length - name.length, length) == name.tokens)
 
+public fun Name.endsWith(name: String): Boolean = endsWith(name.parseAsName())
+
 /**
  * if [this] starts with given [head] name, returns the reminder of the name (could be empty). Otherwise, returns null
  */
diff --git a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/ObservableMetaTest.kt b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/ObservableMetaTest.kt
new file mode 100644
index 00000000..4681ec12
--- /dev/null
+++ b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/ObservableMetaTest.kt
@@ -0,0 +1,49 @@
+package space.kscience.dataforge.meta
+
+import space.kscience.dataforge.names.startsWith
+import kotlin.test.Ignore
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class ObservableMetaTest {
+
+    @Test
+    fun asObservable() {
+        val meta = MutableMeta {
+            "data" put {
+                "x" put ListValue(1, 2, 3)
+                "y" put ListValue(5, 6, 7)
+                "type" put "scatter"
+            }
+        }.asObservable()
+
+        assertEquals("scatter", meta["data.type"].string)
+    }
+
+    @Test
+    @Ignore
+    fun detachNode() {
+        val meta = MutableMeta {
+            "data" put {
+                "x" put ListValue(1, 2, 3)
+                "y" put ListValue(5, 6, 7)
+                "type" put "scatter"
+            }
+        }.asObservable()
+
+        var collector: Value? = null
+
+        meta.onChange(null) { name ->
+            if (name.startsWith("data")) {
+                collector = get("data.z")?.value
+            }
+        }
+
+        val data = meta["data"]!!
+
+        meta.remove("data")
+
+        data["z"] = ListValue(2, 5, 7)
+        assertEquals(null, collector)
+    }
+}
\ No newline at end of file
diff --git a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/descriptors/DescriptorTest.kt b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/descriptors/DescriptorTest.kt
index e8c321fc..1a08ce34 100644
--- a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/descriptors/DescriptorTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/descriptors/DescriptorTest.kt
@@ -11,14 +11,14 @@ class DescriptorTest {
 
     val descriptor = MetaDescriptor {
         node("aNode") {
-            info = "A root demo node"
+            description = "A root demo node"
             value("b", ValueType.NUMBER) {
-                info = "b number value"
+                description = "b number value"
             }
             node("otherNode") {
                 value("otherValue", ValueType.BOOLEAN) {
                     default(false)
-                    info = "default value"
+                    description = "default value"
                 }
             }
         }
diff --git a/dataforge-meta/src/jsMain/kotlin/space/kscience/dataforge/misc/castJs.kt b/dataforge-meta/src/jsMain/kotlin/space/kscience/dataforge/misc/castJs.kt
index b404ebb4..b057bcbe 100644
--- a/dataforge-meta/src/jsMain/kotlin/space/kscience/dataforge/misc/castJs.kt
+++ b/dataforge-meta/src/jsMain/kotlin/space/kscience/dataforge/misc/castJs.kt
@@ -1,5 +1,5 @@
 package space.kscience.dataforge.misc
 import kotlin.js.unsafeCast as unsafeCastJs
 
-@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
-public actual inline fun <T> Any?.unsafeCast(): T = this.unsafeCastJs<T>()
\ No newline at end of file
+@Suppress("NOTHING_TO_INLINE")
+public actual inline fun <T> Any?.unsafeCast(): T = unsafeCastJs<T>()
\ No newline at end of file
diff --git a/dataforge-meta/src/nativeMain/kotlin/space/kscience/dataforge/misc/castNative.kt b/dataforge-meta/src/nativeMain/kotlin/space/kscience/dataforge/misc/castNative.kt
index 27d399fe..4d9aa758 100644
--- a/dataforge-meta/src/nativeMain/kotlin/space/kscience/dataforge/misc/castNative.kt
+++ b/dataforge-meta/src/nativeMain/kotlin/space/kscience/dataforge/misc/castNative.kt
@@ -1,4 +1,4 @@
 package space.kscience.dataforge.misc
 
-@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
+@Suppress("UNCHECKED_CAST")
 public actual inline fun <T> Any?.unsafeCast(): T = this as T
\ No newline at end of file
diff --git a/dataforge-meta/src/wasmJsMain/kotlin/space/kscience/dataforge/misc/castWasm.kt b/dataforge-meta/src/wasmJsMain/kotlin/space/kscience/dataforge/misc/castWasm.kt
new file mode 100644
index 00000000..27d399fe
--- /dev/null
+++ b/dataforge-meta/src/wasmJsMain/kotlin/space/kscience/dataforge/misc/castWasm.kt
@@ -0,0 +1,4 @@
+package space.kscience.dataforge.misc
+
+@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
+public actual inline fun <T> Any?.unsafeCast(): T = this as T
\ No newline at end of file
diff --git a/dataforge-scripting/build.gradle.kts b/dataforge-scripting/build.gradle.kts
index be81fe70..d9d87742 100644
--- a/dataforge-scripting/build.gradle.kts
+++ b/dataforge-scripting/build.gradle.kts
@@ -4,15 +4,15 @@ plugins {
 
 kscience{
     jvm()
-    dependencies {
+    commonMain {
         api(projects.dataforgeWorkspace)
         implementation(kotlin("scripting-common"))
     }
-    dependencies(jvmMain){
+    jvmMain{
         implementation(kotlin("scripting-jvm-host"))
         implementation(kotlin("scripting-jvm"))
     }
-    dependencies(jvmTest){
+    jvmTest{
         implementation(spclibs.logback.classic)
     }
 }
diff --git a/dataforge-workspace/build.gradle.kts b/dataforge-workspace/build.gradle.kts
index ec117865..5fa555eb 100644
--- a/dataforge-workspace/build.gradle.kts
+++ b/dataforge-workspace/build.gradle.kts
@@ -2,29 +2,27 @@ plugins {
     id("space.kscience.gradle.mpp")
 }
 
-kscience{
+kscience {
     jvm()
     js()
     native()
+    wasm()
     useCoroutines()
-    useSerialization{
+    useSerialization {
         protobuf()
     }
-    commonMain{
-        dependencies {
-            api(projects.dataforgeContext)
-            api(projects.dataforgeData)
-            api(projects.dataforgeIo)
-        }
+    commonMain {
+        api(projects.dataforgeContext)
+        api(projects.dataforgeData)
+        api(projects.dataforgeIo)
+
     }
-    jvmTest{
-        dependencies {
-            implementation(spclibs.logback.classic)
-            implementation(projects.dataforgeIo.dataforgeIoYaml)
-        }
+    jvmTest {
+        implementation(spclibs.logback.classic)
+        implementation(projects.dataforgeIo.dataforgeIoYaml)
     }
 }
 
-readme{
+readme {
     maturity = space.kscience.gradle.Maturity.EXPERIMENTAL
 }
\ No newline at end of file
diff --git a/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Task.kt b/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Task.kt
index a1ef7be2..329d9c5a 100644
--- a/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Task.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Task.kt
@@ -9,7 +9,7 @@ import space.kscience.dataforge.meta.MetaRepr
 import space.kscience.dataforge.meta.Specification
 import space.kscience.dataforge.meta.descriptors.Described
 import space.kscience.dataforge.meta.descriptors.MetaDescriptor
-import space.kscience.dataforge.misc.DfId
+import space.kscience.dataforge.misc.DfType
 import space.kscience.dataforge.names.Name
 import space.kscience.dataforge.workspace.Task.Companion.TYPE
 import kotlin.reflect.KType
@@ -19,7 +19,7 @@ import kotlin.reflect.typeOf
  * A configurable task that could be executed on a workspace. The [TaskResult] represents a lazy result of the task.
  * In general no computations should be made until the result is called.
  */
-@DfId(TYPE)
+@DfType(TYPE)
 public interface Task<out T : Any> : Described {
 
     /**
diff --git a/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Workspace.kt b/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Workspace.kt
index ee00f539..37b473db 100644
--- a/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Workspace.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Workspace.kt
@@ -6,7 +6,7 @@ import space.kscience.dataforge.data.DataSet
 import space.kscience.dataforge.data.asSequence
 import space.kscience.dataforge.meta.Meta
 import space.kscience.dataforge.meta.MutableMeta
-import space.kscience.dataforge.misc.DfId
+import space.kscience.dataforge.misc.DfType
 import space.kscience.dataforge.names.Name
 import space.kscience.dataforge.provider.Provider
 
@@ -18,7 +18,7 @@ public interface DataSelector<T: Any>{
 /**
  * An environment for pull-mode computation
  */
-@DfId(Workspace.TYPE)
+@DfType(Workspace.TYPE)
 public interface Workspace : ContextAware, Provider {
     /**
      * The whole data node for current workspace
diff --git a/dataforge-workspace/src/jvmMain/kotlin/space/kscience/dataforge/workspace/fileData.kt b/dataforge-workspace/src/jvmMain/kotlin/space/kscience/dataforge/workspace/fileData.kt
index d9f678b3..ce1b5152 100644
--- a/dataforge-workspace/src/jvmMain/kotlin/space/kscience/dataforge/workspace/fileData.kt
+++ b/dataforge-workspace/src/jvmMain/kotlin/space/kscience/dataforge/workspace/fileData.kt
@@ -11,7 +11,6 @@ import space.kscience.dataforge.data.*
 import space.kscience.dataforge.io.*
 import space.kscience.dataforge.meta.Meta
 import space.kscience.dataforge.meta.copy
-import space.kscience.dataforge.meta.get
 import space.kscience.dataforge.meta.string
 import space.kscience.dataforge.misc.DFExperimental
 import space.kscience.dataforge.misc.DFInternal
@@ -27,10 +26,7 @@ import java.nio.file.WatchEvent
 import java.nio.file.attribute.BasicFileAttributes
 import java.nio.file.spi.FileSystemProvider
 import java.time.Instant
-import kotlin.io.path.extension
-import kotlin.io.path.name
-import kotlin.io.path.nameWithoutExtension
-import kotlin.io.path.readAttributes
+import kotlin.io.path.*
 import kotlin.reflect.KType
 import kotlin.reflect.typeOf
 
@@ -92,7 +88,7 @@ public fun <T : Any> IOPlugin.readDataFile(
 
 
 context(IOPlugin) @DFExperimental
-private fun <T : Any> DataSetBuilder<T>.directory(
+public fun <T : Any> DataSetBuilder<T>.directory(
     path: Path,
     ignoreExtensions: Set<String>,
     formatResolver: FileFormatResolver<T>,
@@ -145,7 +141,7 @@ public inline fun <reified T : Any> IOPlugin.readDataDirectory(
 ): DataTree<T> = readDataDirectory(typeOf<T>(), path, ignoreExtensions, formatResolver)
 
 /**
- * Read raw binary data tree from the directory. All files are read as-is (save for meta files).
+ * Read a raw binary data tree from the directory. All files are read as-is (save for meta files).
  */
 @DFExperimental
 public fun IOPlugin.readRawDirectory(
@@ -260,6 +256,29 @@ public suspend fun <T : Any> IOPlugin.writeDataDirectory(
     }
 }
 
+/**
+ * Reads the specified resources and returns a [DataTree] containing the data.
+ *
+ * @param resources The names of the resources to read.
+ * @param classLoader The class loader to use for loading the resources. By default, it uses the current thread's context class loader.
+ * @return A DataTree containing the data read from the resources.
+ */
+@DFExperimental
+private fun IOPlugin.readResources(
+    vararg resources: String,
+    classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
+): DataTree<Binary> {
+//    require(resource.isNotBlank()) {"Can't mount root resource tree as data root"}
+    return DataTree {
+        resources.forEach { resource ->
+            val path = classLoader.getResource(resource)?.toURI()?.toPath() ?: error(
+                "Resource with name $resource is not resolved"
+            )
+            node(resource, readRawDirectory(path))
+        }
+    }
+}
+
 /**
  * Add file/directory-based data tree item
  *
diff --git a/gradle.properties b/gradle.properties
index 31ef2f9a..3734d13e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,5 +6,5 @@ kotlin.mpp.stability.nowarn=true
 kotlin.incremental.js.ir=true
 kotlin.native.ignoreDisabledTargets=true
 
-toolsVersion=0.15.1-kotlin-1.9.21
+toolsVersion=0.15.2-kotlin-1.9.21
 #kotlin.experimental.tryK2=true
\ No newline at end of file