1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-02-11 20:50:20 +00:00

Compare commits

..

4 Commits

Author SHA1 Message Date
Jeremy Ruston
bb05bd8817 Improve Release Note Presentation (#9657)
* First pass

* UI Tweaks

* Add some colour (possibly too much)

* Corrections

* More tweaks

* Various change note fixes
2026-02-11 18:12:52 +00:00
Mario Pietsch
42a908e1c9 set arrow function parens to always (#9590) 2026-02-10 12:35:10 +01:00
Cameron Fischer
70689a6de4 keyboard-driven-input couldn't use slots well (#9649)
Now it can, at least now without strange and arbitary tweaks to the $depth attribute.
2026-02-10 12:30:08 +01:00
Jeremy Ruston
64ee20edd2 Further MVV fixes (#9645)
* Add ((var)) syntax for passing multi-valued variables through transclude pipeline

Introduce ((var)) attribute syntax to explicitly pass
MVVs to procedures and functions via $transclude, solving the limitation
where <<var>> always resolves to the first value only for backwards
compatibility. Also adds ((var||sep)) and (((filter||sep))) inline display
syntax for debugging MVV values, and multivalued defaults for parameter attributes

* Create pr-draft.md

* Revert "Create pr-draft.md"

This reverts commit dd116af41b.

* Update change note

* Fix linting errors
2026-02-10 12:16:20 +01:00
52 changed files with 1058 additions and 168 deletions

View File

@@ -143,7 +143,14 @@ exports.parseParameterDefinition = function(paramString,options) {
var paramInfo = {name: paramMatch[1]}, var paramInfo = {name: paramMatch[1]},
defaultValue = paramMatch[2] || paramMatch[3] || paramMatch[4] || paramMatch[5]; defaultValue = paramMatch[2] || paramMatch[3] || paramMatch[4] || paramMatch[5];
if(defaultValue !== undefined) { if(defaultValue !== undefined) {
paramInfo["default"] = defaultValue; // Check for an MVV reference ((varname))
var mvvDefaultMatch = /^\(\(([^)|]+)\)\)$/.exec(defaultValue);
if(mvvDefaultMatch) {
paramInfo.defaultType = "multivalue-variable";
paramInfo.defaultVariable = mvvDefaultMatch[1];
} else {
paramInfo["default"] = defaultValue;
}
} }
params.push(paramInfo); params.push(paramInfo);
// Look for the next parameter // Look for the next parameter
@@ -247,6 +254,46 @@ exports.parseMacroInvocationAsTransclusion = function(source,pos) {
return node; return node;
}; };
/*
Look for an MVV (multi-valued variable) reference as a transclusion, i.e. ((varname)) or ((varname params))
Returns null if not found, or a parse tree node of type "transclude" with isMVV: true
*/
exports.parseMVVReferenceAsTransclusion = function(source,pos) {
var node = {
type: "transclude",
isMVV: true,
start: pos,
attributes: {},
orderedAttributes: []
};
// Define our regexps
var reVarName = /([^\s>"'=:)]+)/g;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a double opening parenthesis
var token = $tw.utils.parseTokenString(source,pos,"((");
if(!token) {
return null;
}
pos = token.end;
// Get the variable name
token = $tw.utils.parseTokenRegExp(source,pos,reVarName);
if(!token) {
return null;
}
$tw.utils.addAttributeToParseTreeNode(node,"$variable",token.match[1]);
pos = token.end;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a double closing parenthesis
token = $tw.utils.parseTokenString(source,pos,"))");
if(!token) {
return null;
}
node.end = token.end;
return node;
};
/* /*
Parse macro parameters as attributes. Returns the position after the last attribute Parse macro parameters as attributes. Returns the position after the last attribute
*/ */
@@ -321,19 +368,20 @@ exports.parseMacroParameterAsAttribute = function(source,pos) {
node.type = "indirect"; node.type = "indirect";
node.textReference = indirectValue.match[1]; node.textReference = indirectValue.match[1];
} else { } else {
// Look for a unquoted value // Look for a macro invocation value
var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute); var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
if(unquotedValue) { if(macroInvocation && isNewStyleSeparator) {
pos = unquotedValue.end; pos = macroInvocation.end;
node.type = "string"; node.type = "macro";
node.value = unquotedValue.match[1]; node.value = macroInvocation;
} else { } else {
// Look for a macro invocation value // Look for an MVV reference value
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos); var mvvReference = $tw.utils.parseMVVReferenceAsTransclusion(source,pos);
if(macroInvocation && isNewStyleSeparator) { if(mvvReference && isNewStyleSeparator) {
pos = macroInvocation.end; pos = mvvReference.end;
node.type = "macro"; node.type = "macro";
node.value = macroInvocation; node.value = mvvReference;
node.isMVV = true;
} else { } else {
var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue); var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue);
if(substitutedValue && isNewStyleSeparator) { if(substitutedValue && isNewStyleSeparator) {
@@ -341,6 +389,14 @@ exports.parseMacroParameterAsAttribute = function(source,pos) {
node.type = "substituted"; node.type = "substituted";
node.rawValue = substitutedValue.match[1] || substitutedValue.match[2]; node.rawValue = substitutedValue.match[1] || substitutedValue.match[2];
} else { } else {
// Look for a unquoted value
var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute);
if(unquotedValue) {
pos = unquotedValue.end;
node.type = "string";
node.value = unquotedValue.match[1];
} else {
}
} }
} }
} }
@@ -471,19 +527,20 @@ exports.parseAttribute = function(source,pos) {
node.type = "indirect"; node.type = "indirect";
node.textReference = indirectValue.match[1]; node.textReference = indirectValue.match[1];
} else { } else {
// Look for a unquoted value // Look for a macro invocation value
var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute); var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
if(unquotedValue) { if(macroInvocation) {
pos = unquotedValue.end; pos = macroInvocation.end;
node.type = "string"; node.type = "macro";
node.value = unquotedValue.match[1]; node.value = macroInvocation;
} else { } else {
// Look for a macro invocation value // Look for an MVV reference value
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos); var mvvReference = $tw.utils.parseMVVReferenceAsTransclusion(source,pos);
if(macroInvocation) { if(mvvReference) {
pos = macroInvocation.end; pos = mvvReference.end;
node.type = "macro"; node.type = "macro";
node.value = macroInvocation; node.value = mvvReference;
node.isMVV = true;
} else { } else {
var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue); var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue);
if(substitutedValue) { if(substitutedValue) {
@@ -491,8 +548,16 @@ exports.parseAttribute = function(source,pos) {
node.type = "substituted"; node.type = "substituted";
node.rawValue = substitutedValue.match[1] || substitutedValue.match[2]; node.rawValue = substitutedValue.match[1] || substitutedValue.match[2];
} else { } else {
node.type = "string"; // Look for a unquoted value
node.value = "true"; var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute);
if(unquotedValue) {
pos = unquotedValue.end;
node.type = "string";
node.value = unquotedValue.match[1];
} else {
node.type = "string";
node.value = "true";
}
} }
} }
} }

View File

@@ -32,7 +32,7 @@ Instantiate parse rule
exports.init = function(parser) { exports.init = function(parser) {
this.parser = parser; this.parser = parser;
// Regexp to match // Regexp to match
this.matchRegExp = /\\(function|procedure|widget)\s+([^(\s]+)\((\s*([^)]*))?\)(\s*\r?\n)?/mg; this.matchRegExp = /\\(function|procedure|widget)\s+([^(\s]+)\((\s*([^)]*(?:\)\)[^)]*)*))?\)(\s*\r?\n)?/mg;
}; };
/* /*

View File

@@ -0,0 +1,95 @@
/*\
title: $:/core/modules/parsers/wikiparser/rules/mvvdisplayinline.js
type: application/javascript
module-type: wikirule
Wiki rule for inline display of multi-valued variables and filter results.
Variable display: ((varname)) or ((varname||separator))
Filter display: (((filter))) or (((filter||separator)))
The default separator is ", " (comma space).
\*/
"use strict";
exports.name = "mvvdisplayinline";
exports.types = {inline: true};
exports.init = function(parser) {
this.parser = parser;
};
exports.findNextMatch = function(startPos) {
var source = this.parser.source;
var nextStart = startPos;
while((nextStart = source.indexOf("((",nextStart)) >= 0) {
if(source.charAt(nextStart + 2) === "(") {
// Filter mode: (((filter))) or (((filter||sep)))
var match = /^\(\(\(([\s\S]+?)\)\)\)/.exec(source.substring(nextStart));
if(match) {
// Check for separator: split on last || before )))
var inner = match[1];
var sepIndex = inner.lastIndexOf("||");
if(sepIndex >= 0) {
this.nextMatch = {
type: "filter",
filter: inner.substring(0,sepIndex),
separator: inner.substring(sepIndex + 2),
start: nextStart,
end: nextStart + match[0].length
};
} else {
this.nextMatch = {
type: "filter",
filter: inner,
separator: ", ",
start: nextStart,
end: nextStart + match[0].length
};
}
return nextStart;
}
} else {
// Variable mode: ((varname)) or ((varname||sep))
var match = /^\(\(([^()|]+?)(?:\|\|([^)]*))?\)\)/.exec(source.substring(nextStart));
if(match) {
this.nextMatch = {
type: "variable",
varName: match[1],
separator: match[2] !== undefined ? match[2] : ", ",
start: nextStart,
end: nextStart + match[0].length
};
return nextStart;
}
}
nextStart += 2;
}
return undefined;
};
/*
Parse the most recent match
*/
exports.parse = function() {
var match = this.nextMatch;
this.nextMatch = null;
this.parser.pos = match.end;
var filter, sep = match.separator;
if(match.type === "variable") {
filter = "[(" + match.varName + ")join[" + sep + "]]";
} else {
filter = match.filter + " +[join[" + sep + "]]";
}
return [{
type: "text",
attributes: {
text: {name: "text", type: "filtered", filter: filter}
},
orderedAttributes: [
{name: "text", type: "filtered", filter: filter}
]
}];
};

View File

@@ -61,7 +61,9 @@ ParametersWidget.prototype.execute = function() {
if(name.substr(0,2) === "$$") { if(name.substr(0,2) === "$$") {
name = name.substr(1); name = name.substr(1);
} }
var value = pointer.getTransclusionParameter(name,index,self.getAttribute(attr.name,"")); var defaultValue = (self.multiValuedAttributes && self.multiValuedAttributes[attr.name])
|| self.getAttribute(attr.name,"");
var value = pointer.getTransclusionParameter(name,index,defaultValue);
self.setVariable(name,value); self.setVariable(name,value);
}); });
// Assign any metaparameters // Assign any metaparameters
@@ -80,7 +82,8 @@ ParametersWidget.prototype.execute = function() {
if(name.substr(0,2) === "$$") { if(name.substr(0,2) === "$$") {
name = name.substr(1); name = name.substr(1);
} }
var value = self.getAttribute(attr.name,""); var value = (self.multiValuedAttributes && self.multiValuedAttributes[attr.name])
|| self.getAttribute(attr.name,"");
self.setVariable(name,value); self.setVariable(name,value);
}); });
} }

View File

@@ -158,8 +158,10 @@ Collect string parameters
TranscludeWidget.prototype.collectStringParameters = function() { TranscludeWidget.prototype.collectStringParameters = function() {
var self = this; var self = this;
this.stringParametersByName = Object.create(null); this.stringParametersByName = Object.create(null);
this.multiValuedParametersByName = Object.create(null);
if(!this.legacyMode) { if(!this.legacyMode) {
$tw.utils.each(this.attributes,function(value,name) { $tw.utils.each(this.attributes,function(value,name) {
var attrName = name; // Save original attribute name for MVV lookup
if(name.charAt(0) === "$") { if(name.charAt(0) === "$") {
if(name.charAt(1) === "$") { if(name.charAt(1) === "$") {
// Attributes starting $$ represent parameters starting with a single $ // Attributes starting $$ represent parameters starting with a single $
@@ -170,6 +172,9 @@ TranscludeWidget.prototype.collectStringParameters = function() {
} }
} }
self.stringParametersByName[name] = value; self.stringParametersByName[name] = value;
if(self.multiValuedAttributes && self.multiValuedAttributes[attrName]) {
self.multiValuedParametersByName[name] = self.multiValuedAttributes[attrName];
}
}); });
} }
}; };
@@ -313,7 +318,16 @@ TranscludeWidget.prototype.parseTransclusionTarget = function(parseAsInline) {
if(name.charAt(0) === "$") { if(name.charAt(0) === "$") {
name = "$" + name; name = "$" + name;
} }
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],name,param["default"]) if(param.defaultType === "multivalue-variable") {
// Construct MVV attribute for the default
var mvvNode = {type: "transclude", isMVV: true, attributes: {}, orderedAttributes: []};
$tw.utils.addAttributeToParseTreeNode(mvvNode,"$variable",param.defaultVariable);
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],{
name: name, type: "macro", isMVV: true, value: mvvNode
});
} else {
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],name,param["default"]);
}
}); });
} else if(srcVariable && !srcVariable.isFunctionDefinition) { } else if(srcVariable && !srcVariable.isFunctionDefinition) {
// For macros and ordinary variables, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__" // For macros and ordinary variables, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
@@ -364,7 +378,11 @@ TranscludeWidget.prototype.getOrderedTransclusionParameters = function() {
// Collect the parameters // Collect the parameters
for(var name in this.stringParametersByName) { for(var name in this.stringParametersByName) {
var value = this.stringParametersByName[name]; var value = this.stringParametersByName[name];
result.push({name: name, value: value}); var param = {name: name, value: value};
if(this.multiValuedParametersByName[name]) {
param.multiValue = this.multiValuedParametersByName[name];
}
result.push(param);
} }
// Sort numerical parameter names first // Sort numerical parameter names first
result.sort(function(a,b) { result.sort(function(a,b) {
@@ -394,10 +412,16 @@ Fetch the value of a parameter
*/ */
TranscludeWidget.prototype.getTransclusionParameter = function(name,index,defaultValue) { TranscludeWidget.prototype.getTransclusionParameter = function(name,index,defaultValue) {
if(name in this.stringParametersByName) { if(name in this.stringParametersByName) {
if(this.multiValuedParametersByName[name]) {
return this.multiValuedParametersByName[name];
}
return this.stringParametersByName[name]; return this.stringParametersByName[name];
} else { } else {
var name = "" + index; var name = "" + index;
if(name in this.stringParametersByName) { if(name in this.stringParametersByName) {
if(this.multiValuedParametersByName[name]) {
return this.multiValuedParametersByName[name];
}
return this.stringParametersByName[name]; return this.stringParametersByName[name];
} }
} }

View File

@@ -381,19 +381,31 @@ filterFn: only include attributes where filterFn(name) returns true
Widget.prototype.computeAttributes = function(options) { Widget.prototype.computeAttributes = function(options) {
options = options || {}; options = options || {};
var changedAttributes = {}, var changedAttributes = {},
self = this; self = this,
newMultiValuedAttributes = Object.create(null);
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) { $tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
if(options.filterFn) { if(options.filterFn) {
if(!options.filterFn(name)) { if(!options.filterFn(name)) {
return; return;
} }
} }
var value = self.computeAttribute(attribute); var value = self.computeAttribute(attribute),
if(self.attributes[name] !== value) { multiValue = null;
if($tw.utils.isArray(value)) {
multiValue = value;
newMultiValuedAttributes[name] = multiValue;
value = value[0] || "";
}
var changed = (self.attributes[name] !== value);
if(!changed && multiValue && self.multiValuedAttributes) {
changed = !$tw.utils.isArrayEqual(self.multiValuedAttributes[name] || [], multiValue);
}
if(changed) {
self.attributes[name] = value; self.attributes[name] = value;
changedAttributes[name] = true; changedAttributes[name] = true;
} }
}); });
this.multiValuedAttributes = newMultiValuedAttributes;
return changedAttributes; return changedAttributes;
}; };
@@ -431,7 +443,7 @@ Widget.prototype.computeAttribute = function(attribute,options) {
}); });
// Invoke the macro // Invoke the macro
var variableInfo = this.getVariableInfo(macroName,{params: params}); var variableInfo = this.getVariableInfo(macroName,{params: params});
if(options.asList) { if(options.asList || attribute.isMVV) {
value = variableInfo.resultList; value = variableInfo.resultList;
} else { } else {
value = variableInfo.text; value = variableInfo.text;

View File

@@ -122,15 +122,15 @@ tags: $:/tags/Macro
\whitespace trim \whitespace trim
\procedure keyboard-driven-input-actions() \procedure keyboard-driven-input-actions()
<%if [<event-key-descriptor>match[((input-accept))]] %> <%if [<event-key-descriptor>match[((input-accept))]] %>
<<inputAcceptActions>> <$transclude $variable=inputAcceptActions $fillignore=yes />
<%elseif [<event-key-descriptor>match[((input-accept-variant))]] %> <%elseif [<event-key-descriptor>match[((input-accept-variant))]] %>
<<inputAcceptVariantActions>> <$transclude $variable=inputAcceptVariantActions $fillignore=yes />
<%elseif [<event-key-descriptor>match[((input-up))]] %> <%elseif [<event-key-descriptor>match[((input-up))]] %>
<<input-next-actions-before>> <$transclude $variable=input-next-actions-before $fillignore=yes />
<%elseif [<event-key-descriptor>match[((input-down))]] %> <%elseif [<event-key-descriptor>match[((input-down))]] %>
<<input-next-actions-after>> <$transclude $variable=input-next-actions-after $fillignore=yes />
<%elseif [<event-key-descriptor>match[((input-cancel))]] %> <%elseif [<event-key-descriptor>match[((input-cancel))]] %>
<<inputCancelActions>> <$transclude $variable=inputCancelActions $fillignore=yes />
<%endif%> <%endif%>
\end keyboard-driven-input-actions \end keyboard-driven-input-actions

View File

@@ -0,0 +1,16 @@
title: MultiValuedVariables/AttributeFirstValue
description: ((var)) on non-MVV-aware widget attribute returns first value only
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
<$let items={{{ [all[tiddlers]sort[]] }}}>
<$text text=((items))/>
</$let>
+
title: ExpectedResult
<p>$:/core</p>
+

View File

@@ -0,0 +1,19 @@
title: MultiValuedVariables/DefaultParameterMVV
description: Procedure default parameter value using ((var)) syntax to provide MVV default
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
\procedure showItems(itemList:((defaults)))
<$text text={{{ [(itemList)join[-]] }}}/>
\end
<$let defaults={{{ [all[tiddlers]sort[]] }}}>
<<showItems>>
</$let>
+
title: ExpectedResult
<p>$:/core-ExpectedResult-Output</p>
+

View File

@@ -0,0 +1,16 @@
title: MultiValuedVariables/InlineDisplay
description: ((var)) in inline wikitext displays MVV with default comma-space separator
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
<$let items={{{ [all[tiddlers]sort[]] }}}>
((items))
</$let>
+
title: ExpectedResult
<p>$:/core, ExpectedResult, Output</p>
+

View File

@@ -0,0 +1,16 @@
title: MultiValuedVariables/InlineDisplaySeparator
description: ((var||separator)) in inline wikitext displays MVV with custom separator
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
<$let items={{{ [all[tiddlers]sort[]] }}}>
((items||:))
</$let>
+
title: ExpectedResult
<p>$:/core:ExpectedResult:Output</p>
+

View File

@@ -0,0 +1,14 @@
title: MultiValuedVariables/InlineFilterDisplay
description: (((filter))) in inline wikitext displays filter results with default comma-space separator
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
((([all[tiddlers]sort[]])))
+
title: ExpectedResult
<p>$:/core, ExpectedResult, Output</p>
+

View File

@@ -0,0 +1,14 @@
title: MultiValuedVariables/InlineFilterDisplaySeparator
description: (((filter||separator))) in inline wikitext displays filter results with custom separator
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
((([all[tiddlers]sort[]]||:)))
+
title: ExpectedResult
<p>$:/core:ExpectedResult:Output</p>
+

View File

@@ -0,0 +1,19 @@
title: MultiValuedVariables/TranscludeParameter
description: Multi-valued variable passed as procedure parameter via ((var)) syntax
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
\procedure showItems(itemList)
<$text text={{{ [(itemList)join[-]] }}}/>
\end
<$let items={{{ [all[tiddlers]sort[]] }}}>
<$transclude $variable="showItems" itemList=((items))/>
</$let>
+
title: ExpectedResult
<p>$:/core-ExpectedResult-Output</p>
+

View File

@@ -0,0 +1,17 @@
title: MultiValuedVariables/TranscludeParameterFunction
description: Multi-valued variable passed as function parameter via ((var)) in $transclude
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
\function showItems(itemList) [(itemList)sort[]join[-]]
<$let items={{{ [all[tiddlers]] }}}>
<$transclude $variable="showItems" itemList=((items))/>
</$let>
+
title: ExpectedResult
<p>$:/core-ExpectedResult-Output</p>
+

View File

@@ -22,6 +22,6 @@ There is also a single line form for shorter functions:
\function <function-name>(<param-name>[:<param-default-value>],<param-name>[:<param-default-value>]...) <single-line-definition-text> \function <function-name>(<param-name>[:<param-default-value>],<param-name>[:<param-default-value>]...) <single-line-definition-text>
``` ```
The first line of the definition specifies the function name and any parameters. Each parameter has a name and, optionally, a default value that is used if no value is supplied on a particular call to the function. The lines that follow contain the text of the function (i.e. the snippet represented by the function name), until `\end` appears on a line by itself: The first line of the definition specifies the function name and any parameters. Each parameter has a name and, optionally, a default value that is used if no value is supplied on a particular call to the function. <<.from-version "5.4.0">> The default value can also be a [[multi-valued variable reference|Multi-Valued Variables]] using the `((var))` syntax (e.g. `\function show(items:((defaults)))`). The lines that follow contain the text of the function (i.e. the snippet represented by the function name), until `\end` appears on a line by itself:
See [[Functions]] for more details. See [[Functions]] for more details.

View File

@@ -22,7 +22,7 @@ There is also a single line form for shorter procedures:
\procedure <procedure-name>(<param-name>[:<param-default-value>],<param-name>[:<param-default-value>]...) <single-line-definition-text> \procedure <procedure-name>(<param-name>[:<param-default-value>],<param-name>[:<param-default-value>]...) <single-line-definition-text>
``` ```
The first line of the definition specifies the procedure name and any parameters. Each parameter has a name and, optionally, a default value that is used if no value is supplied on a particular call to the procedure. The lines that follow contain the text of the procedure text (i.e. the snippet represented by the procedure name), until `\end` appears on a line by itself: The first line of the definition specifies the procedure name and any parameters. Each parameter has a name and, optionally, a default value that is used if no value is supplied on a particular call to the procedure. <<.from-version "5.4.0">> The default value can also be a [[multi-valued variable reference|Multi-Valued Variables]] using the `((var))` syntax (e.g. `\procedure show(items:((defaults)))`). The lines that follow contain the text of the procedure text (i.e. the snippet represented by the procedure name), until `\end` appears on a line by itself:
For example: For example:

View File

@@ -4,7 +4,7 @@ release: 5.4.0
tags: $:/tags/ChangeNote tags: $:/tags/ChangeNote
change-type: enhancement change-type: enhancement
change-category: hackability change-category: hackability
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/8972 https://github.com/TiddlyWiki/TiddlyWiki5/pull/9614 github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/8972 https://github.com/TiddlyWiki/TiddlyWiki5/pull/9614 https://github.com/TiddlyWiki/TiddlyWiki5/pull/9645
github-contributors: Jermolene saqimtiaz github-contributors: Jermolene saqimtiaz
This PR introduces a new filter run prefix `:let` that assigns the result of the filter run to a variable that is made available for the remaining filter runs of the filter expression. It solves the problem that previously it was impossible to compute values for filter operator parameters; parameters could only be a literal string, text reference or variable reference. This PR introduces a new filter run prefix `:let` that assigns the result of the filter run to a variable that is made available for the remaining filter runs of the filter expression. It solves the problem that previously it was impossible to compute values for filter operator parameters; parameters could only be a literal string, text reference or variable reference.

View File

@@ -1,4 +1,4 @@
change-category: internal change-category: developer
change-type: enhancement change-type: enhancement
created: 20260120153052332 created: 20260120153052332
description: Adds a destroy method for widgets allowing for clean up of resources when a widget is removed. description: Adds a destroy method for widgets allowing for clean up of resources when a widget is removed.

View File

@@ -4,5 +4,5 @@ release: 5.4.0
tags: $:/tags/ChangeNote tags: $:/tags/ChangeNote
change-type: performance change-type: performance
change-category: internal change-category: internal
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9118 github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9119
github-contributors: Leilei332 github-contributors: Leilei332

View File

@@ -4,5 +4,5 @@ release: 5.4.0
tags: $:/tags/ChangeNote tags: $:/tags/ChangeNote
change-type: deprecation change-type: deprecation
change-category: internal change-category: internal
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9118 github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9131
github-contributors: Leilei332 github-contributors: Leilei332

View File

@@ -1,9 +1,9 @@
title: $:/changenotes/5.4.0/#9177 title: $:/changenotes/5.4.0/#9177
description: Add Display Field to list-tagged-draggable and list-links-draggable description: Add Display Field to list-tagged-draggable and list-links-draggable macros
release: 5.4.0 release: 5.4.0
tags: $:/tags/ChangeNote tags: $:/tags/ChangeNote
change-type: enhancement change-type: enhancement
change-category: usability change-category: hackability
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9177 github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9177
github-contributors: kookma github-contributors: kookma

View File

@@ -7,4 +7,4 @@ change-category: internal
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9235 https://github.com/TiddlyWiki/TiddlyWiki5/pull/9325 github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9235 https://github.com/TiddlyWiki/TiddlyWiki5/pull/9325
github-contributors: Leilei332 github-contributors: Leilei332
Removes nested `<span class="tc-keyboard">` element in core search field. Removes nested `<span class="tc-keyboard">` element in core search field.

View File

@@ -1,4 +1,4 @@
change-category: usability change-category: hackability
change-type: enhancement change-type: enhancement
created: 20251115012214040 created: 20251115012214040
description: list-links-draggable defaults to currentTiddler if tiddler parameter is missing description: list-links-draggable defaults to currentTiddler if tiddler parameter is missing

View File

@@ -4,7 +4,7 @@ release: 5.4.0
tags: $:/tags/ChangeNote tags: $:/tags/ChangeNote
change-type: enhancement change-type: enhancement
change-category: internal change-category: internal
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9287 github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9287 https://github.com/TiddlyWiki/TiddlyWiki5/pull/9657
github-contributors: Jermolene github-contributors: Jermolene
Doing so enables us to filter and group changes. For example, we could show all the breaking changes between two releases. Doing so enables us to filter and group changes. For example, we could show all the breaking changes between two releases.

View File

@@ -3,7 +3,7 @@ description: Add the prevailing mimetype for CSV parser
release: 5.4.0 release: 5.4.0
tags: $:/tags/ChangeNote tags: $:/tags/ChangeNote
change-type: enhancement change-type: enhancement
change-category: internal change-category: developer
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9445 github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9445
github-contributors: EvidentlyCube github-contributors: EvidentlyCube

View File

@@ -3,7 +3,7 @@ description: Import UI now conditionally displays file-type-specific import opti
tags: $:/tags/ChangeNote tags: $:/tags/ChangeNote
release: 5.4.0 release: 5.4.0
change-type: enhancement change-type: enhancement
change-category: hackability change-category: usability
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9465 github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9465
github-contributors: linonetwo github-contributors: linonetwo

View File

@@ -5,6 +5,6 @@ tags: $:/tags/ChangeNote
change-type: bugfix change-type: bugfix
change-category: internal change-category: internal
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9570 github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9570
github-contributors: jermolene github-contributors: Jermolene
Fixes issue whereby transcluding a _canonical_uri tiddler with a missing image did not assign the progress classes `tc-image-loading`, `tc-image-loaded` and `tc-image-error`. Fixes issue whereby transcluding a _canonical_uri tiddler with a missing image did not assign the progress classes `tc-image-loading`, `tc-image-loaded` and `tc-image-error`.

View File

@@ -3,7 +3,7 @@ created: 20260106174849522
modified: 20260106174849522 modified: 20260106174849522
tags: $:/tags/ChangeNote tags: $:/tags/ChangeNote
change-type: enhancement change-type: enhancement
change-category: usability change-category: hackability
description: The button classes of Editor toolbar buttons can now be evaluated as filter expressions description: The button classes of Editor toolbar buttons can now be evaluated as filter expressions
release: 5.4.0 release: 5.4.0
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9585 github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9585

View File

@@ -1,7 +1,7 @@
change-category: widget change-category: widget
change-type: enhancement change-type: enhancement
created: 20260125124838970 created: 20260125124838970
description: adds support for pointer capture and enabling/disabling the widget description: Adds support for pointer capture and enabling/disabling to the EventCatcherWidget.
github-contributors: saqimtiaz github-contributors: saqimtiaz
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9609 github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9609
modified: 20260125202924515 modified: 20260125202924515

View File

@@ -1,4 +1,4 @@
change-category: usability change-category: hackability
change-type: enhancement change-type: enhancement
created: 20260124121646761 created: 20260124121646761
description: The simple toc macro now supports a level parameter description: The simple toc macro now supports a level parameter

View File

@@ -1,4 +1,4 @@
change-category: usability change-category: hackability
change-type: enhancement change-type: enhancement
created: 20260126125300 created: 20260126125300
description: Add start- and endactions to link-widget, list-links-draggable and list-tagged-draggable macros description: Add start- and endactions to link-widget, list-links-draggable and list-tagged-draggable macros

View File

@@ -3,7 +3,7 @@ description: Add "Cascade Details" to the Advanced tab of the Tiddler Info panel
tags: $:/tags/ChangeNote tags: $:/tags/ChangeNote
release: 5.4.0 release: 5.4.0
change-type: enhancement change-type: enhancement
change-category: hackability change-category: usability
github-links: https://github.com/Jermolene/TiddlyWiki5/pull/9634 https://github.com/Jermolene/TiddlyWiki5/pull/9643 github-links: https://github.com/Jermolene/TiddlyWiki5/pull/9634 https://github.com/Jermolene/TiddlyWiki5/pull/9643
github-contributors: DesignThinkerer github-contributors: DesignThinkerer

View File

@@ -3,11 +3,11 @@ description: Background actions and media query tracking
tags: $:/tags/ChangeNote tags: $:/tags/ChangeNote
release: 5.4.0 release: 5.4.0
change-type: enhancement change-type: enhancement
change-category: internal change-category: hackability
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9641 github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9641
github-contributors: Jermolene github-contributors: Jermolene
Introduces[[Background Actions]] that are triggered whenever there is a change to the results of a specified filter. Introduces [[Background Actions]] that are triggered whenever there is a change to the results of a specified filter.
Building on that, it also introduces a new [[Media Query Tracker Mechanism]] that can track the results of any CSS media query (not just dark mode), storing the results in a shadow `$:/info/...` tiddler Building on that, it also introduces a new [[Media Query Tracker Mechanism]] that can track the results of any CSS media query (not just dark mode), storing the results in a shadow `$:/info/...` tiddler

View File

@@ -7,9 +7,9 @@ type: text/vnd.tiddlywiki
description: Under development description: Under development
\procedure release-introduction() \procedure release-introduction()
Release v5.4.0 is an important release because it deliberately and forensically loosens backwards compatibility where needed to allow significant new features and fundamental improvements to be made. Release v5.4.0 deliberately and forensically loosens backwards compatibility to clear the path for significant new features and fundamental improvements to be made in the future.
''Please note that this release note is not yet fully completed, please see the change history on ~GitHub for the full list of changes included in this release.'' ''Please note that there are some changes that do not yet change notes, please see the change history on ~GitHub for the full list of changes.''
See the [[project plan|https://github.com/orgs/TiddlyWiki/projects/4]] for full details. See the [[project plan|https://github.com/orgs/TiddlyWiki/projects/4]] for full details.
\end release-introduction \end release-introduction

View File

@@ -3,7 +3,7 @@ description: Draft title internationalization
release: 5.4.0 release: 5.4.0
tags: $:/tags/ChangeNote tags: $:/tags/ChangeNote
change-type: enhancement change-type: enhancement
change-category: internal change-category: translation
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/8891 github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/8891
github-contributors: Leilei332 github-contributors: Leilei332

View File

@@ -53,3 +53,13 @@ Impact note fields:
* `description` - a brief description of the impact * `description` - a brief description of the impact
* `impact-type` - the type of impact; see [[here|Release Notes and Changes]] for valid values * `impact-type` - the type of impact; see [[here|Release Notes and Changes]] for valid values
* `text` - description of the impact if required * `text` - description of the impact if required
! Summary Lists
Summary sections are curated via lists stored in fields on the tiddler `$:/tw5.com/releases/info/summary-lists`.
Each field is named `<section>/<release>` and contains a list of change note tiddler titles, for example:
* `highlights/5.4.0`
* `big-bug-fixes/5.4.0`
* `clearing-the-decks/5.4.0`

View File

@@ -1,35 +1,75 @@
title: $:/tw5.com/releases/info/ title: $:/tw5.com/releases/info/
category-order: translation plugin usability theme hackability widget filters performance nodejs internal developer category-order: translation usability hackability performance widget filters plugin theme nodejs developer internal
categories/internal/caption: Internal
categories/translation/caption: Translation summary-section-order: highlights big-bug-fixes clearing-the-decks
categories/plugin/caption: Plugin change-type-order: feature enhancement performance bugfix deprecation security
categories/widget/caption: Widget impact-type-order: compatibility-break deprecation pluginisation
categories/usability/caption: Usability tab-order: [[Summary]] [[Change Types]] [[Change Categories]] [[Impacts]]
categories/theme/caption: Theme categories/developer/singular: Developer
categories/hackability/caption: Hackability categories/developer/plural: Developer
categories/nodejs/caption: Node.js categories/filters/singular: Filters
categories/performance/caption: Performance categories/filters/plural: Filters
categories/developer/caption: Developer categories/hackability/singular: Hackability
categories/filters/caption: Filters categories/hackability/plural: Hackability
change-types/bugfix/caption: Bugfix categories/internal/singular: Internal
categories/internal/plural: Internal
categories/nodejs/singular: Node.js
categories/nodejs/plural: Node.js
categories/performance/singular: Performance
categories/performance/plural: Performance
categories/plugin/singular: Plugin
categories/plugin/plural: Plugins
categories/theme/singular: Theme
categories/theme/plural: Theming
categories/translation/singular: Translation
categories/translation/plural: Translation
categories/usability/singular: Usability
categories/usability/plural: Usability
categories/widget/singular: Widget
categories/widget/plural: Widgets
change-types/bugfix/singular: Bugfix
change-types/bugfix/colour: #ffe246 change-types/bugfix/colour: #ffe246
change-types/feature/caption: Feature change-types/bugfix/plural: Bugfixes
change-types/feature/colour: #91ba66 change-types/deprecation/singular: Deprecation
change-types/enhancement/caption: Enhancement
change-types/enhancement/colour: #cba5ff
change-types/deprecation/caption: Deprecation
change-types/deprecation/colour: #ff9d6c change-types/deprecation/colour: #ff9d6c
change-types/security/caption: Security change-types/deprecation/plural: Deprecations
change-types/security/colour: #ff6666 change-types/enhancement/singular: Enhancement
change-types/performance/caption: Performance change-types/enhancement/colour: #cba5ff
change-types/enhancement/plural: Enhancements
change-types/feature/singular: Feature
change-types/feature/colour: #91ba66
change-types/feature/plural: Features
change-types/performance/singular: Performance
change-types/performance/colour: #c2c7ff change-types/performance/colour: #c2c7ff
impact-types/deprecation/caption: Deprecation change-types/performance/plural: Performance
impact-types/deprecation/colour/foreground: #882222 change-types/security/singular: Security
impact-types/deprecation/colour/background: #ffdddd change-types/security/colour: #ff6666
impact-types/compatibility-break/caption: Compatibility Break change-types/security/plural: Security
impact-types/compatibility-break/colour/foreground: #228822 impact-types/compatibility-break/singular: Compatibility Break
impact-types/compatibility-break/colour/background: #ddffdd impact-types/compatibility-break/colour/background: #ddffdd
impact-types/pluginisation/caption: Pluginisation impact-types/compatibility-break/colour/foreground: #228822
impact-types/pluginisation/colour/foreground: #222288 impact-types/compatibility-break/plural: Compatibility Breaks
impact-types/deprecation/singular: Deprecation
impact-types/deprecation/colour/background: #ffdddd
impact-types/deprecation/colour/foreground: #882222
impact-types/deprecation/plural: Deprecations
impact-types/pluginisation/singular: Pluginisation
impact-types/pluginisation/colour/background: #ddddff impact-types/pluginisation/colour/background: #ddddff
impact-types/pluginisation/colour/foreground: #222288
impact-types/pluginisation/plural: Pluginisations
summary-sections/big-bug-fixes/plural: Big Bug Fixes
summary-sections/clearing-the-decks/plural: Clearing the Decks
summary-sections/highlights/plural: Highlights
summary-sections/highlights/default-state: open
categories/translation/colour: #7ec8e3
categories/usability/colour: #f4a261
categories/hackability/colour: #9b8ec4
categories/performance/colour: #87ceeb
categories/widget/colour: #e78ac3
categories/filters/colour: #8cc56d
categories/plugin/colour: #f0c674
categories/theme/colour: #d4a76a
categories/nodejs/colour: #77bb66
categories/developer/colour: #6baed6
categories/internal/colour: #b0b0b0

View File

@@ -48,69 +48,295 @@ tags: $:/tags/Global
</span> </span>
\end impact-pill \end impact-pill
\procedure change-list() \procedure release-section-toggle(state,default)
\whitespace trim \whitespace trim
<div class="doc-release-note"> <$reveal type="nomatch" state=<<state>> text="open" default=<<default>> tag="span">
<$list filter="[enlist{$:/tw5.com/releases/info/category-order}]" variable="category"> <$action-setfield $tiddler=<<state>> $field="text" $value="open"/>
<%if [tag[$:/tags/ChangeNote]] :filter[{!!release}match<release>] :filter[{!!change-category}match<category>] %> <span class="doc-release-section-toggle">{{$:/core/images/right-arrow}}</span>
<div class="doc-release-note-heading"> </$reveal>
<h2 class="doc-change-note-category"> <$reveal type="match" state=<<state>> text="open" default=<<default>> tag="span">
<$text text={{{ [[$:/tw5.com/releases/info/categories/]addsuffix<category>addsuffix[/caption]get[text]] }}} /> <$action-setfield $tiddler=<<state>> $field="text" $value="close"/>
</h2> <span class="doc-release-section-toggle">{{$:/core/images/down-arrow}}</span>
</div> </$reveal>
<$list filter="[tag[$:/tags/ChangeNote]] :filter[{!!release}match<release>] :filter[{!!change-category}match<category>sort[description]]" variable="change"> \end release-section-toggle
<div class="doc-change-note-item">
<h3 class="doc-change-note-heading"> \procedure change-note-type-badge()
<$link \whitespace trim
to="Release Notes and Changes" <$link
class="doc-link-badge" to="Release Notes and Changes"
style.backgroundColor={{{ [<change>get[change-type]addprefix[$:/tw5.com/releases/info/change-types/]addsuffix[/colour]get[text]] }}} class="doc-link-badge"
> style.backgroundColor={{{ [<change>get[change-type]addprefix[$:/tw5.com/releases/info/change-types/]addsuffix[/colour]get[text]] }}}
<$text text={{{ [<change>get[change-type]addprefix[$:/tw5.com/releases/info/change-types/]addsuffix[/caption]get[text]] }}}/> >
</$link> <$transclude $tiddler=<<change>> $field="description" $mode="inline"/></h3> <$text text={{{ [<change>get[change-type]addprefix[$:/tw5.com/releases/info/change-types/]addsuffix[/singular]get[text]] }}}/>
<div class="doc-change-note-info-list"> </$link>
<$list filter="[tag[$:/tags/ImpactNote]] :filter[{!!changenote}match<change>] +[sort[description]]" variable="impact" counter="impactCount"> \end change-note-type-badge
<div class="doc-change-note-info-list-item-caption">
<%if [<impactCount>match[1]] %> \procedure change-note-category-badge()
Impact: \whitespace trim
<%endif%> <$link
</div> to="Release Notes and Changes"
<div class="doc-change-note-info-list-item-description"> class="doc-link-badge"
<div class="doc-change-note-info-list-item-description-summary"> style.backgroundColor={{{ [<change>get[change-category]addprefix[$:/tw5.com/releases/info/categories/]addsuffix[/colour]get[text]] }}}
<$transclude $variable="impact-pill" $mode="inline" impact-type={{{ [<impact>get[impact-type]] }}}/> >
<$text text=": "/> <$text text={{{ [<change>get[change-category]addprefix[$:/tw5.com/releases/info/categories/]addsuffix[/singular]get[text]] }}}/>
<$transclude $tiddler=<<impact>> $field="description" $mode="inline"/> </$link>
</div> \end change-note-category-badge
<div class="doc-change-note-info-list-item-description-details">
<$transclude $tiddler=<<impact>> $field="text" $mode="block"/> \procedure change-note-heading()
</div> \whitespace trim
</div> <h3 class="doc-change-note-heading"
</$list> style.backgroundColor={{{ [<change>get[change-type]addprefix[$:/tw5.com/releases/info/change-types/]addsuffix[/colour]get[text]addsuffix[40]] }}}
<div class="doc-change-note-info-list-item-caption"> >
<$text text="GitHub: "/> <%if [<show-type-badge>!match[no]] %><<change-note-type-badge>><%endif%>
</div> <%if [<show-category-badge>match[yes]] %><<change-note-category-badge>><%endif%>
<div class="doc-change-note-info-list-item-description"> <%if [<show-tiddler-link>match[yes]] %><$link to=<<change>> class="doc-change-note-description-link"><$transclude $tiddler=<<change>> $field="description" $mode="inline"/></$link><%else%><$transclude $tiddler=<<change>> $field="description" $mode="inline"/><%endif%>
<$list filter="[<change>get[github-links]enlist-input[]]" variable="link"> </h3>
<a \end change-note-heading
href=<<link>>
class="doc-github-link" \procedure change-note-impacts()
target="_blank" \whitespace trim
rel="noopener noreferrer" <$list filter="[tag[$:/tags/ImpactNote]] :filter[{!!changenote}match<change>] +[sort[description]]" variable="impact" counter="impactCount">
> <div class="doc-change-note-info-list-item-caption">
{{$:/core/images/github}} <%if [<impactCount>match[1]] %>Impact:<%endif%>
</a> </div>
</$list> <div class="doc-change-note-info-list-item-description">
</div> <div class="doc-change-note-info-list-item-description-summary">
</div> <$transclude $variable="impact-pill" $mode="inline" impact-type={{{ [<impact>get[impact-type]] }}}/>
<div class="doc-change-note-description"> <$text text=": "/>
<$transclude $tiddler=<<change>> $mode="block"/> <%if [<show-tiddler-link>match[yes]] %><$link to=<<impact>> class="doc-change-note-description-link"><$transclude $tiddler=<<impact>> $field="description" $mode="inline"/></$link><%else%><$transclude $tiddler=<<impact>> $field="description" $mode="inline"/><%endif%>
</div> </div>
</div> <div class="doc-change-note-info-list-item-description-details">
</$list> <$transclude $tiddler=<<impact>> $field="text" $mode="block"/>
<%endif%> </div>
</div>
</$list>
\end change-note-impacts
\procedure change-note-github-links()
\whitespace trim
<div class="doc-change-note-info-list-item-caption">
<$text text="GitHub: "/>
</div>
<div class="doc-change-note-info-list-item-description">
<$list filter="[<change>get[github-links]enlist-input[]]" variable="link">
<a href=<<link>> class="doc-github-link" target="_blank" rel="noopener noreferrer">
{{$:/core/images/github}}
</a>
</$list> </$list>
</div> </div>
\end change-list \end change-note-github-links
\procedure change-note-info()
\whitespace trim
<div class="doc-change-note-info-list">
<%if [<show-change-impacts>!match[no]] %><<change-note-impacts>><%endif%>
<<change-note-github-links>>
</div>
\end change-note-info
\procedure change-note-description()
\whitespace trim
<div class="doc-change-note-description">
<$transclude $tiddler=<<change>> $mode="block"/>
</div>
\end change-note-description
\procedure change-note-item()
\whitespace trim
<div class="doc-change-note-item">
<<change-note-heading>>
<<change-note-info>>
<<change-note-description>>
</div>
\end change-note-item
\procedure change-note-list(filter)
\whitespace trim
<$list
filter=<<filter>>
variable="change"
emptyMessage="<div class='doc-change-note-empty'>No curated changes yet.</div>"
>
<<change-note-item>>
</$list>
\end change-note-list
\procedure summary-section()
\whitespace trim
<$let
summaryState={{{ [<release>addprefix[$:/state/release/summary/]addsuffix[/]addsuffix<summary-key>] }}}
summaryDefault={{{ [[$:/tw5.com/releases/info/summary-sections/]addsuffix<summary-key>addsuffix[/default-state]get[text]] }}}
summaryListTiddler="$:/tw5.com/releases/info/summary-lists"
summaryListField={{{ [<summary-key>addsuffix[/]addsuffix<release>] }}}
>
<$let summaryFilter="[<summaryListTiddler>get<summaryListField>enlist-input[]]">
<div class="doc-release-section">
<$button class="tc-btn-invisible doc-release-section-summary">
<$macrocall $name="release-section-toggle" state=<<summaryState>> default=<<summaryDefault>>/>
<span class="doc-release-section-summary-text">
<$text text={{{ [[$:/tw5.com/releases/info/summary-sections/]addsuffix<summary-key>addsuffix[/plural]get[text]] }}} />
<$text text=" "/><span class="doc-release-section-count">(<$text text={{{ [<summaryListTiddler>get<summaryListField>enlist-input[]] +[count[]] }}}/>)</span>
</span>
</$button>
<$reveal type="match" state=<<summaryState>> text="open" default=<<summaryDefault>> tag="div" class="doc-release-section-body">
<$macrocall $name="change-note-list" filter=<<summaryFilter>>/>
</$reveal>
</div>
</$let>
</$let>
\end summary-section
\procedure summary-tab()
\whitespace trim
<$let show-type-badge="yes" show-category-badge="yes" show-change-impacts="yes" show-tiddler-link="yes">
<div class="doc-release-summary">
<$list filter="[enlist{$:/tw5.com/releases/info/summary-section-order}]" variable="summary-key">
<div class="doc-release-summary-section">
<$macrocall $name="summary-section"/>
</div>
</$list>
</div>
</$let>
\end summary-tab
\procedure change-type-section()
\whitespace trim
<$let changeTypeState={{{ [<release>addprefix[$:/state/release/change-type/]addsuffix[/]addsuffix<change-type>] }}}>
<div class="doc-release-section">
<$button class="tc-btn-invisible doc-release-section-summary"
style.backgroundColor={{{ [[$:/tw5.com/releases/info/change-types/]addsuffix<change-type>addsuffix[/colour]get[text]addsuffix[40]] }}}
>
<$macrocall $name="release-section-toggle" state=<<changeTypeState>>/>
<span class="doc-release-section-summary-text">
<$text text={{{ [[$:/tw5.com/releases/info/change-types/]addsuffix<change-type>addsuffix[/plural]get[text]] }}} />
<$text text=" "/><span class="doc-release-section-count">(<$text text={{{ [tag[$:/tags/ChangeNote]] :filter[{!!release}match<release>] :filter[{!!change-type}match<change-type>] +[count[]] }}}/>)</span>
</span>
</$button>
<$reveal type="match" state=<<changeTypeState>> text="open" tag="div" class="doc-release-section-body">
<$let typeFilter="[tag[$:/tags/ChangeNote]] :filter[{!!release}match<release>] :filter[{!!change-type}match<change-type>] +[sort[description]]">
<$macrocall $name="change-note-list" filter=<<typeFilter>>/>
</$let>
</$reveal>
</div>
</$let>
\end change-type-section
\procedure change-types-tab()
\whitespace trim
<$let show-type-badge="no" show-category-badge="yes" show-change-impacts="yes" show-tiddler-link="yes">
<$list filter="[enlist{$:/tw5.com/releases/info/change-type-order}]" variable="change-type">
<<change-type-section>>
</$list>
</$let>
\end change-types-tab
\procedure change-category-section()
\whitespace trim
<$let changeCategoryState={{{ [<release>addprefix[$:/state/release/change-category/]addsuffix[/]addsuffix<category>] }}}>
<div class="doc-release-section">
<$button class="tc-btn-invisible doc-release-section-summary"
style.backgroundColor={{{ [[$:/tw5.com/releases/info/categories/]addsuffix<category>addsuffix[/colour]get[text]addsuffix[40]] }}}
>
<$macrocall $name="release-section-toggle" state=<<changeCategoryState>>/>
<span class="doc-release-section-summary-text">
<$text text={{{ [[$:/tw5.com/releases/info/categories/]addsuffix<category>addsuffix[/plural]get[text]] }}} />
<$text text=" "/><span class="doc-release-section-count">(<$text text={{{ [tag[$:/tags/ChangeNote]] :filter[{!!release}match<release>] :filter[{!!change-category}match<category>] +[count[]] }}}/>)</span>
</span>
</$button>
<$reveal type="match" state=<<changeCategoryState>> text="open" tag="div" class="doc-release-section-body">
<$let categoryFilter="[tag[$:/tags/ChangeNote]] :filter[{!!release}match<release>] :filter[{!!change-category}match<category>] +[sort[description]]">
<$macrocall $name="change-note-list" filter=<<categoryFilter>>/>
</$let>
</$reveal>
</div>
</$let>
\end change-category-section
\procedure change-categories-tab()
\whitespace trim
<$let show-type-badge="yes" show-category-badge="no" show-change-impacts="yes" show-tiddler-link="yes">
<$list filter="[enlist{$:/tw5.com/releases/info/category-order}]" variable="category">
<<change-category-section>>
</$list>
</$let>
\end change-categories-tab
\procedure impact-change-note()
\whitespace trim
<$let change={{{ [<impact>get[changenote]] }}} show-change-impacts="no">
<<change-note-item>>
</$let>
\end impact-change-note
\procedure impact-note-item()
\whitespace trim
<div class="doc-impact-note-item">
<h3 class="doc-impact-note-heading"
style.backgroundColor={{{ [<impact>get[impact-type]addprefix[$:/tw5.com/releases/info/impact-types/]addsuffix[/colour/background]get[text]] }}}
>
<$transclude $variable="impact-pill" $mode="inline" impact-type={{{ [<impact>get[impact-type]] }}}/>
<$text text=": "/>
<%if [<show-tiddler-link>match[yes]] %><$link to=<<impact>> class="doc-change-note-description-link"><$transclude $tiddler=<<impact>> $field="description" $mode="inline"/></$link><%else%><$transclude $tiddler=<<impact>> $field="description" $mode="inline"/><%endif%>
</h3>
<div class="doc-impact-note-details">
<$transclude $tiddler=<<impact>> $field="text" $mode="block"/>
</div>
<<impact-change-note>>
</div>
\end impact-note-item
\procedure impact-type-section()
\whitespace trim
<$let impactTypeState={{{ [<release>addprefix[$:/state/release/impact-type/]addsuffix[/]addsuffix<impact-type>] }}}>
<div class="doc-release-section">
<$button class="tc-btn-invisible doc-release-section-summary"
style.backgroundColor={{{ [[$:/tw5.com/releases/info/impact-types/]addsuffix<impact-type>addsuffix[/colour/background]get[text]] }}}
>
<$macrocall $name="release-section-toggle" state=<<impactTypeState>>/>
<span class="doc-release-section-summary-text">
<$text text={{{ [[$:/tw5.com/releases/info/impact-types/]addsuffix<impact-type>addsuffix[/plural]get[text]] }}} />
<$text text=" "/><span class="doc-release-section-count">(<$text text={{{ [tag[$:/tags/ImpactNote]] :filter[{!!impact-type}match<impact-type>] :filter[{!!changenote}get[release]match<release>] +[count[]] }}}/>)</span>
</span>
</$button>
<$reveal type="match" state=<<impactTypeState>> text="open" tag="div" class="doc-release-section-body">
<$list
filter="[tag[$:/tags/ImpactNote]] :filter[{!!impact-type}match<impact-type>] :filter[{!!changenote}get[release]match<release>] +[sort[description]]"
variable="impact"
>
<<impact-note-item>>
</$list>
</$reveal>
</div>
</$let>
\end impact-type-section
\procedure impacts-tab()
\whitespace trim
<$let show-tiddler-link="yes">
<$list filter="[enlist{$:/tw5.com/releases/info/impact-type-order}]" variable="impact-type">
<<impact-type-section>>
</$list>
</$let>
\end impacts-tab
\procedure credits-tab()
\whitespace trim
<div class="doc-release-credits">
A warm thank you to the developers who have contributed to this release:
<<acknowledgements>>
</div>
\end credits-tab
\procedure release-tabs()
\whitespace trim
<$macrocall
$name="tabs"
tabsList="[enlist{$:/tw5.com/releases/info/tab-order}]"
default="Summary"
class="doc-release-tabs"
template="$:/tw5.com/releases/tab-template"
/>
\end release-tabs
\procedure acknowledgements() \procedure acknowledgements()
<ol class="doc-github-contributors"> <ol class="doc-github-contributors">
@@ -136,21 +362,13 @@ See <<github-release-changes-link>> and [[other releases|TiddlyWiki Releases]].
<<banner-credits>> <<banner-credits>>
! Introduction
<$transclude $variable="release-introduction" $mode="block"/> <$transclude $variable="release-introduction" $mode="block"/>
! Changes <<release-tabs>>
<$transclude $variable="change-list" $mode="inline"/> ! Credits
<!-- Acknowledgement list --> <<credits-tab>>
! Acknowledgements
A warm thank you to the developers who have contributed to this release:
<<acknowledgements>>
</$let> </$let>
\end releasenote \end releasenote

View File

@@ -5,6 +5,57 @@ type: text/vnd.tiddlywiki
.doc-release-note { .doc-release-note {
} }
.tc-tab-set.doc-release-tabs {
margin-top: 1rem;
}
.doc-release-summary-section {
margin-bottom: 0.75em;
}
.doc-release-section {
border: 1px solid #e8e8e8;
border-radius: 0.5em;
margin: 0.75em 0;
background: #ffffff;
}
.doc-release-section-summary {
display: flex;
align-items: center;
padding: 0.5em 0.75em;
font-weight: 600;
background: #f1f1f1;
width: 100%;
cursor: pointer;
text-align: left;
border: none;
font-size: inherit;
font-family: inherit;
border-radius: 0.5em 0.5em 0 0;
}
.doc-release-section-toggle {
margin-right: 0.5em;
}
.doc-release-section-summary-text {
flex: 1 1 auto;
}
.doc-release-section-count {
font-weight: normal;
opacity: 0.6;
}
.doc-release-credits {
margin: 0.75em 0;
}
.doc-release-section-body {
padding: 0.5em 0.75em;
}
.doc-release-note-heading { .doc-release-note-heading {
} }
@@ -84,3 +135,56 @@ type: text/vnd.tiddlywiki
padding: 0.25em; padding: 0.25em;
background: #ffffff; background: #ffffff;
} }
.doc-impact-note-item {
border: 1px solid #e8e8e8;
border-radius: 0.5em;
margin: 0.5em 0;
padding: 0.5em;
background: #f9f9f9;
}
.doc-impact-note-heading {
font-weight: 600;
margin: 0 0 0.25em 0;
padding: 0.25em 0.5em;
border-radius: 0.25em;
}
.doc-impact-note-item > .doc-change-note-item {
font-size: 0.9em;
}
.doc-impact-note-details {
font-size: 0.9em;
margin-bottom: 0.5em;
}
.doc-impact-note-change {
display: flex;
gap: 0.5em;
align-items: baseline;
flex-wrap: wrap;
}
.doc-impact-note-change-caption {
text-transform: uppercase;
font-size: 0.7em;
font-weight: bold;
white-space: nowrap;
}
.doc-impact-note-change-link {
font-weight: 600;
}
.doc-change-note-description-link {
font-weight: inherit;
color: inherit;
text-decoration: underline;
text-decoration-style: dotted;
}
.doc-change-note-description-link:hover {
text-decoration-style: solid;
}

View File

@@ -0,0 +1,5 @@
title: $:/tw5.com/releases/info/summary-lists
type: text/vnd.tiddlywiki
highlights/5.4.0: [[$:/changenotes/5.4.0/#9055]] [[$:/changenotes/5.4.0/#9641]] [[$:/changenotes/5.4.0/#8972]]
big-bug-fixes/5.4.0: [[$:/changenotes/5.4.0/#9259]]
clearing-the-decks/5.4.0:

View File

@@ -0,0 +1,14 @@
title: $:/tw5.com/releases/tab-template
type: text/vnd.tiddlywiki
<%if [<currentTab>match[Summary]] %>
<<summary-tab>>
<%elseif [<currentTab>match[Change Types]] %>
<<change-types-tab>>
<%elseif [<currentTab>match[Change Categories]] %>
<<change-categories-tab>>
<%elseif [<currentTab>match[Impacts]] %>
<<impacts-tab>>
<%elseif [<currentTab>match[Credits]] %>
<<credits-tab>>
<%endif%>

View File

@@ -0,0 +1,6 @@
title: $:/tw5.com/releases/ViewTemplate/body/changenote
\whitespace trim
<$let change=<<currentTiddler>> show-type-badge="yes" show-category-badge="yes" show-change-impacts="yes">
<<change-note-item>>
</$let>

View File

@@ -0,0 +1,6 @@
title: $:/tw5.com/releases/ViewTemplateBodyFilters
tags: $:/tags/ViewTemplateBodyFilter
list-before:
[tag[$:/tags/ChangeNote]then[$:/tw5.com/releases/ViewTemplate/body/changenote]]
[tag[$:/tags/ImpactNote]then[$:/tw5.com/releases/ViewTemplate/body/impactnote]]

View File

@@ -0,0 +1,6 @@
title: $:/tw5.com/releases/ViewTemplate/body/impactnote
\whitespace trim
<$let impact=<<currentTiddler>> show-tiddler-link="yes">
<<impact-note-item>>
</$let>

View File

@@ -8,4 +8,6 @@ type: text/vnd.tiddlywiki
|!how declared|!behaviour| |!how declared|!behaviour|
|\define|Textual substitution of parameters is performed on the body text. No further processing takes place. The result after textual substitution is used as the attribute's value| |\define|Textual substitution of parameters is performed on the body text. No further processing takes place. The result after textual substitution is used as the attribute's value|
|<<.wlink SetWidget>>, <<.wlink LetWidget>>, <<.wlink VarsWidget>>, \procedure, \widget|Body text is retrieved as-is and used as the attribute's value.| |<<.wlink SetWidget>>, <<.wlink LetWidget>>, <<.wlink VarsWidget>>, \procedure, \widget|Body text is retrieved as-is and used as the attribute's value.|
|\function|When a function (e.g. `.myfun`) is invoked as `<div class=<<.myfun>>/>`, it is a synonym for `<div class={{{[function[.myfun]]}}}/>`. As with any filtered transclusion (i.e. triple curly braces), all results except the first are discarded. That first result is used as the attribute's value. Note that functions are recursively processed even when invoked in this form. In other words a filter expression in a function can invoke another function and the processing will continue| |\function|When a function (e.g. `.myfun`) is invoked as `<div class=<<.myfun>>/>`, it is a synonym for `<div class={{{[function[.myfun]]}}}/>`. As with any filtered transclusion (i.e. triple curly braces), all results except the first are discarded. That first result is used as the attribute's value. Note that functions are recursively processed even when invoked in this form. In other words a filter expression in a function can invoke another function and the processing will continue|
<<.from-version "5.4.0">> Using the [[multi-valued variable attribute|Multi-Valued Variable Attribute Values]] syntax `((var))` instead of `<<var>>` passes the complete list of values to the attribute rather than just the first value. This is primarily useful for passing [[multi-valued variables|Multi-Valued Variables]] to procedure and function parameters via the TranscludeWidget.

View File

@@ -59,14 +59,57 @@ For example:
``` ```
\function myfunc(tiddlers) [(tiddlers)sort[]] \function myfunc(tiddlers) [(tiddlers)sort[]]
<$let varname={{{ [all[tiddlers]limit[50]] }}}> <$let varname={{{ [all[tiddlers]limit[50]] }}}>
<$text text={{{ [function[myfunc],(varname)] +[join[-]] }}}/> <$text text={{{ [function[myfunc],(varname)] +[join[-]] }}}/>
</$let> </$let>
``` ```
! Examples !! Passing Multi-Valued Variables to Procedures and Functions
For example: <<.from-version "5.4.0">> Multi-valued variables can be passed to procedures and functions using the `((var))` syntax in [[widget attributes|Multi-Valued Variable Attribute Values]]. This is the multi-valued counterpart of the `<<var>>` syntax:
```
\procedure showItems(itemList)
<$text text={{{ [(itemList)join[-]] }}}/>
\end
<$let items={{{ [all[tiddlers]sort[]] }}}>
<$transclude $variable="showItems" itemList=((items))/>
</$let>
```
The `((var))` syntax can also be used in procedure and function parameter defaults:
```
\procedure showItems(itemList:((defaults)))
<$text text={{{ [(itemList)join[-]] }}}/>
\end
```
! Displaying Multi-Valued Variables
<<.from-version "5.4.0">> Multi-valued variables can be displayed inline in wikitext using the `((var))` syntax. The values are joined with a comma and space by default:
```
<$let items={{{ [all[tiddlers]sort[]] }}}>
((items))
</$let>
```
A custom separator can be specified using the `||` delimiter:
```
((items||:))
```
A similar syntax with triple round brackets is available for displaying the results of a filter expression:
```
((( [all[tiddlers]sort[]] )))
((( [all[tiddlers]sort[]] ||: )))
```
! Examples
``` ```
<$let varname={{{ [all[tiddlers]sort[]] }}}> <$let varname={{{ [all[tiddlers]sort[]] }}}>

View File

@@ -0,0 +1,37 @@
created: 20250208120000000
modified: 20250208120000000
tags: WikiText [[Widget Attributes]]
title: Multi-Valued Variable Attribute Values
type: text/vnd.tiddlywiki
<<.from-version "5.4.0">> Multi-valued variable attribute values are indicated with double round brackets around the variable name. This passes the complete list of values of a [[multi-valued variable|Multi-Valued Variables]] to the attribute, rather than just the first value.
```
<$transclude $variable="myproc" items=((myvar))/>
```
This is the multi-valued counterpart to the [[Variable Attribute Values]] syntax `<<var>>`, which only returns the first value for backwards compatibility. The relationship mirrors the existing convention in filter operands where `<var>` returns a single value and `(var)` returns all values.
! Non-MVV-Aware Attributes
When `((var))` is used on a widget attribute that does not support multi-valued variables (such as the `text` attribute of the TextWidget), only the first value is used:
```
<$text text=((myvar))/>
```
! Passing Multi-Valued Variables to Procedures
The primary use case for this syntax is passing multi-valued variables through the `$transclude` pipeline to procedures and functions:
```
\procedure showItems(itemList)
<$text text={{{ [(itemList)join[-]] }}}/>
\end
<$let items={{{ [all[tiddlers]sort[]] }}}>
<$transclude $variable="showItems" itemList=((items))/>
</$let>
```
In this example, the complete list of tiddler titles stored in `items` is passed to the `itemList` parameter of the `showItems` procedure.

View File

@@ -62,6 +62,28 @@ or, when used with a template, `{{{ [tag[mechanism]]||TemplateTitle }}}` expands
<<.tip "Install the //Internals// plugin to enable the display of the generated widget tree in the preview pane of the editor">> <<.tip "Install the //Internals// plugin to enable the display of the generated widget tree in the preview pane of the editor">>
!! Multi-Valued Variable Display
<<.from-version "5.4.0">> The `((var))` syntax can be used inline to display the values of a [[multi-valued variable|Multi-Valued Variables]], joined with a comma and space by default:
```
((myvar))
((myvar||:))
```
The optional `||` delimiter specifies a custom separator string.
!! Inline Filter Display
<<.from-version "5.4.0">> The `(((filter)))` syntax displays the results of a filter expression inline, joined with a comma and space by default:
```
((( [all[tiddlers]sort[]] )))
((( [all[tiddlers]sort[]] ||: )))
```
The optional `||` delimiter specifies a custom separator string. This is the inline display counterpart to the filtered transclusion `{{{ }}}` syntax.
--- ---
See also: See also:

View File

@@ -11,6 +11,7 @@ Attributes of [[HTML elements|HTML in WikiText]] and widgets can be specified in
* [[a transclusion of a macro/variable|Variable Attribute Values]] * [[a transclusion of a macro/variable|Variable Attribute Values]]
* [[as the result of a filter expression|Filtered Attribute Values]] * [[as the result of a filter expression|Filtered Attribute Values]]
* <<.from-version "5.3.0">> [[as the result of performing filter and variable substitutions on the given string|Substituted Attribute Values]] * <<.from-version "5.3.0">> [[as the result of performing filter and variable substitutions on the given string|Substituted Attribute Values]]
* <<.from-version "5.4.0">> [[as a multi-valued variable reference|Multi-Valued Variable Attribute Values]]
|attribute type|syntax|h |attribute type|syntax|h
|literal |single, double or triple quotes or no quotes for values without spaces | |literal |single, double or triple quotes or no quotes for values without spaces |
@@ -18,9 +19,10 @@ Attributes of [[HTML elements|HTML in WikiText]] and widgets can be specified in
|variable |double angle brackets around a macro or variable invocation | |variable |double angle brackets around a macro or variable invocation |
|filtered |triple curly braces around a filter expression| |filtered |triple curly braces around a filter expression|
|substituted|single or triple backticks around the text to be processed for substitutions| |substituted|single or triple backticks around the text to be processed for substitutions|
|multi-valued variable |double round brackets around a variable name |
<$list filter="[[Literal Attribute Values]] [[Transcluded Attribute Values]] [[Variable Attribute Values]] [[Filtered Attribute Values]] [[Substituted Attribute Values]]"> <$list filter="[[Literal Attribute Values]] [[Transcluded Attribute Values]] [[Variable Attribute Values]] [[Filtered Attribute Values]] [[Substituted Attribute Values]] [[Multi-Valued Variable Attribute Values]]">
<$link><h1><$text text=<<currentTiddler>>/></h1></$link> <$link><h1><$text text=<<currentTiddler>>/></h1></$link>
<$transclude mode="block"/> <$transclude mode="block"/>
</$list> </$list>

View File

@@ -58,7 +58,7 @@ js.configs.recommended,
"array-callback-return": "off", "array-callback-return": "off",
"@stylistic/array-element-newline": "off", "@stylistic/array-element-newline": "off",
"arrow-body-style": "error", "arrow-body-style": "error",
"@stylistic/arrow-parens": ["error", "as-needed"], "@stylistic/arrow-parens": ["error", "always"],
"@stylistic/arrow-spacing": ["error", { "@stylistic/arrow-spacing": ["error", {
after: true, after: true,
before: true, before: true,

45
prompt.md Normal file
View File

@@ -0,0 +1,45 @@
# Release Notes Redesign Prompt (TiddlyWiki5)
You are editing the TiddlyWiki5 repo, focusing on the release notes presentation under `editions/tw5.com/tiddlers/releasenotes/`.
## Goals
- Present release notes with tabs:
- **Summary**: collapsible sections for Highlights, Big Bug Fixes, Clearing the Decks (curated lists).
- **Change Types**
- **Change Categories**
- **Impacts**
- **Credits** (acknowledgements)
- Headings must be customisable via the releases info tiddler.
- Use TiddlyWiki idioms: state stored in tiddlers (not DOM).
- Keep procedures small (≈20 lines), with sensible identifiers.
## Current Implementation (after recent edits)
- `editions/tw5.com/tiddlers/releasenotes/ReleasesProcedures.tid`
- Reworked into many small procedures.
- Tabs use `$:/core/macros/tabs` and template `$:/tw5.com/releases/tab-template`.
- Sections use `$reveal` + `$action-setfield` toggles; state stored in `$:/state/release/...`.
- Summary pulls curated lists from `$:/tw5.com/releases/info/summary-lists` (fields named `<section>/<release>`).
- Change Types/Categories/Impacts list change notes by filters; Credits tab shows acknowledgements.
- `editions/tw5.com/tiddlers/releasenotes/ReleasesTabTemplate.tid`
- Uses `<$list match>` to choose the tab body: Summary, Change Types, Change Categories, Impacts, Credits.
- `editions/tw5.com/tiddlers/releasenotes/ReleasesInfo.multids`
- Added tab order, summary sections & captions/order, change/impact orders.
- `editions/tw5.com/tiddlers/releasenotes/ReleasesStyles.tid`
- Styles for tabs, section headers, toggle icons, impact cards.
- `editions/tw5.com/tiddlers/releasenotes/ReleasesSummaryLists.tid`
- Example curated fields for 5.4.0 (Highlights, Big Bug Fixes, Clearing the Decks) using wiki-links.
## Outstanding Issue
- Summary tab still shows raw keys (e.g., “highlights”) and not the curated change lists. Captions/list lookup likely still failing; needs robust caption lookup and ensuring summary lists are read correctly (field names and filters).
- Other tabs (types/categories/impacts) expand/collapse correctly.
## Open Files in IDE (for context)
- ReleasesProcedures.tid
- ReleasesStyles.tid
- Release Notes and Changes Internals.tid
- Release 5.3.8.tid
- Release 5.4.0.tid
## Reminder
- Avoid adding fields to change notes; curation is via list fields on `$:/tw5.com/releases/info/summary-lists`.
- Keep using tiddler-store state; no DOM state.