Skip to content

Commit 0cb59d2

Browse files
committed
update sdk and skills
1 parent 8833a84 commit 0cb59d2

27 files changed

Lines changed: 582 additions & 144 deletions

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,8 +936,10 @@ Renderers should use **text size, letter spacing, weight, and animation** to com
936936
| `[legato]` | Normal | Tighter (-0.5px) | Normal | Smooth underline or wave |
937937
| `[staccato]` | Normal | Wider (+1–2px) | Bold | Dotted underline or dashes between words |
938938
| `[energy:1–4]` | Smaller (90%) | Normal | Light | Lower opacity |
939+
| `[energy:5–6]` | Normal | Normal | Normal | Default styling (no special treatment) |
939940
| `[energy:7–10]` | Larger (110–130%) | Normal | Bold | Glow or pulsing effect |
940941
| `[melody:1–3]` | Normal | Normal | Normal | Flat visual indicator (dash) |
942+
| `[melody:4–6]` | Normal | Normal | Normal | Default styling (no special treatment) |
941943
| `[melody:7–10]` | Normal | Normal | Normal | Wave or oscillating indicator |
942944

943945
These are **recommendations**, not requirements. Renderers may adapt the visual treatment to their platform. The key principle: the reader should **feel** the delivery instruction from the visual presentation alone.

SDK/dotnet/src/ManagedCode.Tps/AGENTS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,10 @@ For this .NET project:
6666
- Keep the `ManagedCode.Tps` namespace prefix intact.
6767
- Do not place test helpers or test-only code in this project.
6868
- Prefer small, composable types over large utility buckets.
69+
70+
## Exception Record
71+
72+
- Size exception:
73+
- scope: `TpsPlaybackSession.cs`, `Internal/TpsContentCompiler.cs`, `Internal/TpsParser.cs`, `Models/Contracts.cs`
74+
- reason: these files still concentrate the parity-first .NET implementation of the runtime contract
75+
- removal plan: split playback transition/publish code, parser header/front-matter code, content-scope handling, and transport contracts into smaller cohesive types

SDK/dotnet/src/ManagedCode.Tps/Models/PlaybackContracts.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,27 @@ public enum TpsPlaybackStatus
1111
Completed
1212
}
1313

14+
public static class TpsPlaybackDefaults
15+
{
16+
public const int DefaultSpeedStepWpm = 10;
17+
public const int DefaultTickIntervalMs = 16;
18+
public const int MinimumSpeedStepWpm = 1;
19+
public const int MinimumTickIntervalMs = 1;
20+
}
21+
22+
public static class TpsPlaybackEventNames
23+
{
24+
public const string StateChanged = "stateChanged";
25+
public const string WordChanged = "wordChanged";
26+
public const string PhraseChanged = "phraseChanged";
27+
public const string BlockChanged = "blockChanged";
28+
public const string SegmentChanged = "segmentChanged";
29+
public const string StatusChanged = "statusChanged";
30+
public const string Completed = "completed";
31+
public const string SnapshotChanged = "snapshotChanged";
32+
public const string ObserveSnapshot = "observeSnapshot";
33+
}
34+
1435
public class TpsPlaybackSessionOptions
1536
{
1637
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
@@ -29,14 +50,14 @@ public int SpeedStepWpm
2950
{
3051
get;
3152
init => field = value > 0 ? value : throw new ArgumentOutOfRangeException(nameof(value), "SpeedStepWpm must be greater than zero.");
32-
} = 10;
53+
} = TpsPlaybackDefaults.DefaultSpeedStepWpm;
3354

3455
[JsonPropertyName("tickIntervalMs")]
3556
public int TickIntervalMs
3657
{
3758
get;
3859
init => field = value > 0 ? value : throw new ArgumentOutOfRangeException(nameof(value), "TickIntervalMs must be greater than zero.");
39-
} = 16;
60+
} = TpsPlaybackDefaults.DefaultTickIntervalMs;
4061

4162
[JsonIgnore]
4263
public TimeProvider TimeProvider

SDK/dotnet/src/ManagedCode.Tps/TpsPlaybackSession.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public IDisposable ObserveSnapshot(Action<TpsPlaybackSnapshot> observer, bool em
104104

105105
if (emitCurrent)
106106
{
107-
InvokeSnapshotObserver("observeSnapshot", observer, CreateSnapshot(), subscription);
107+
InvokeSnapshotObserver(TpsPlaybackEventNames.ObserveSnapshot, observer, CreateSnapshot(), subscription);
108108
}
109109

110110
return subscription;
@@ -685,45 +685,45 @@ private void Publish(Transition transition)
685685
{
686686
if (transition.StatusChangedRaised)
687687
{
688-
InvokeHandlers("statusChanged", StatusChanged, new TpsPlaybackStatusChangedEventArgs(transition.State, transition.PreviousStatus, transition.Status), transition.Snapshot);
688+
InvokeHandlers(TpsPlaybackEventNames.StatusChanged, StatusChanged, new TpsPlaybackStatusChangedEventArgs(transition.State, transition.PreviousStatus, transition.Status), transition.Snapshot);
689689
}
690690

691691
if (transition.StateChangedRaised)
692692
{
693693
var args = new TpsPlaybackStateChangedEventArgs(transition.State, transition.PreviousState, transition.Status);
694-
InvokeHandlers("stateChanged", StateChanged, args, transition.Snapshot);
694+
InvokeHandlers(TpsPlaybackEventNames.StateChanged, StateChanged, args, transition.Snapshot);
695695

696696
if (transition.WordChangedRaised)
697697
{
698-
InvokeHandlers("wordChanged", WordChanged, args, transition.Snapshot);
698+
InvokeHandlers(TpsPlaybackEventNames.WordChanged, WordChanged, args, transition.Snapshot);
699699
}
700700

701701
if (transition.PhraseChangedRaised)
702702
{
703-
InvokeHandlers("phraseChanged", PhraseChanged, args, transition.Snapshot);
703+
InvokeHandlers(TpsPlaybackEventNames.PhraseChanged, PhraseChanged, args, transition.Snapshot);
704704
}
705705

706706
if (transition.BlockChangedRaised)
707707
{
708-
InvokeHandlers("blockChanged", BlockChanged, args, transition.Snapshot);
708+
InvokeHandlers(TpsPlaybackEventNames.BlockChanged, BlockChanged, args, transition.Snapshot);
709709
}
710710

711711
if (transition.SegmentChangedRaised)
712712
{
713-
InvokeHandlers("segmentChanged", SegmentChanged, args, transition.Snapshot);
713+
InvokeHandlers(TpsPlaybackEventNames.SegmentChanged, SegmentChanged, args, transition.Snapshot);
714714
}
715715

716716
if (transition.CompletedRaised)
717717
{
718-
InvokeHandlers("completed", Completed, args, transition.Snapshot);
718+
InvokeHandlers(TpsPlaybackEventNames.Completed, Completed, args, transition.Snapshot);
719719
}
720720
}
721721
else if (transition.CompletedRaised)
722722
{
723-
InvokeHandlers("completed", Completed, new TpsPlaybackStateChangedEventArgs(transition.State, transition.PreviousState, transition.Status), transition.Snapshot);
723+
InvokeHandlers(TpsPlaybackEventNames.Completed, Completed, new TpsPlaybackStateChangedEventArgs(transition.State, transition.PreviousState, transition.Status), transition.Snapshot);
724724
}
725725

726-
InvokeHandlers("snapshotChanged", SnapshotChanged, new TpsPlaybackSnapshotChangedEventArgs(transition.Snapshot), transition.Snapshot);
726+
InvokeHandlers(TpsPlaybackEventNames.SnapshotChanged, SnapshotChanged, new TpsPlaybackSnapshotChangedEventArgs(transition.Snapshot), transition.Snapshot);
727727
}
728728

729729
private void InvokeHandlers<TEventArgs>(string eventName, EventHandler<TEventArgs>? handlers, TEventArgs args, TpsPlaybackSnapshot snapshot)

SDK/dotnet/tests/ManagedCode.Tps.Tests/TpsPlaybackSessionTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,26 @@ public void PlaybackSession_ObserveSnapshot_DoesNotReplayAfterImmediateDispose()
374374
Assert.Empty(snapshots);
375375
}
376376

377+
[Fact]
378+
public void PlaybackSession_ObserveSnapshot_CanSkipInitialReplay_AndExplicitOffStopsFurtherEvents()
379+
{
380+
var script = TpsRuntime.Compile("## [Signal]\n### [Body]\nReady now.").Script;
381+
using var session = new TpsPlaybackSession(script);
382+
var snapshots = new List<TpsPlaybackSnapshot>();
383+
var statuses = new List<TpsPlaybackStatus>();
384+
385+
using var subscription = session.ObserveSnapshot(snapshot => snapshots.Add(snapshot), emitCurrent: false);
386+
EventHandler<TpsPlaybackSnapshotChangedEventArgs> handler = (_, args) => statuses.Add(args.Snapshot.Status);
387+
session.SnapshotChanged += handler;
388+
389+
session.NextWord();
390+
session.SnapshotChanged -= handler;
391+
session.PreviousWord();
392+
393+
Assert.Equal("now.", snapshots[0].State.CurrentWord?.CleanText);
394+
Assert.Equal([TpsPlaybackStatus.Paused], statuses);
395+
}
396+
377397
[Fact]
378398
public void StandalonePlayer_CompilesSourceAndExposesRuntimeSnapshotAndControls()
379399
{

SDK/dotnet/tests/ManagedCode.Tps.Tests/TpsRuntimeTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ public void PublicSurface_ExposesConstantsAndContracts()
1212
Assert.Equal(140, TpsSpec.DefaultBaseWpm);
1313
Assert.Equal("neutral", TpsSpec.DefaultEmotion);
1414
Assert.Equal("WPM", TpsSpec.WpmSuffix);
15+
Assert.Equal(10, TpsPlaybackDefaults.DefaultSpeedStepWpm);
16+
Assert.Equal(16, TpsPlaybackDefaults.DefaultTickIntervalMs);
17+
Assert.Equal("snapshotChanged", TpsPlaybackEventNames.SnapshotChanged);
1518
Assert.Contains(TpsSpec.Tags.Building, TpsSpec.DeliveryModes);
1619
Assert.Contains(TpsSpec.Tags.Loud, TpsSpec.VolumeLevels);
1720
Assert.Contains(TpsSpec.Tags.Normal, TpsSpec.RelativeSpeedTags);

SDK/flutter/AGENTS.md

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,58 @@
1-
# ManagedCode.Tps Flutter SDK
1+
# AGENTS.md
2+
3+
Project: SDK Flutter runtime
4+
Owned by: ManagedCode.Tps SDK maintainers
5+
6+
Parent: `../../AGENTS.md`
7+
8+
## Purpose
9+
10+
- Own the Dart runtime consumed by Flutter hosts.
11+
- Keep compile, validate, restore, player, playback-session, and standalone-player behavior aligned with the other TPS runtimes.
12+
13+
## Entry Points
14+
15+
- `lib/src/managedcode_tps.dart`
16+
- `lib/managedcode_tps.dart`
17+
- `test/runtime_test.dart`
18+
19+
## Boundaries
20+
21+
- In scope:
22+
- Dart runtime implementation
23+
- Flutter-facing package export surface
24+
- parity and integration tests for the Dart runtime
25+
- Out of scope:
26+
- widget-layer rendering
27+
- TypeScript, .NET, Swift, or Java source changes unless parity requires them
28+
29+
## Project Commands
30+
31+
- `build`: `dart analyze`
32+
- `test`: `dart test`
33+
- `coverage`: `./coverage.sh`
34+
35+
## Applicable Skills
36+
37+
- `mcaf-solid-maintainability`
38+
- `mcaf-testing`
39+
- `dotnet-mcaf-documentation`
40+
41+
## Local Risks Or Protected Areas
42+
43+
- Shared fixture parity with `SDK/fixtures/**` and `examples/*.tps` is a protected contract.
44+
- Public runtime models must stay embeddable and UI-framework-neutral.
45+
- Playback snapshots and event names are transport-facing and must remain stable across runtimes.
46+
47+
## Local Rules
248

3-
- Runtime contract must stay aligned with `SDK/ts`, `SDK/js`, and `SDK/dotnet`.
4-
- Validate against `SDK/fixtures/**` and `examples/*.tps`.
5-
- Keep the package Flutter-compatible and UI-framework-neutral.
649
- Prefer immutable public models and deterministic playback math.
50+
- Keep runtime constants under named SDK types instead of scattering literals through the player/session code.
51+
- Add regression tests for every playback or compiled-JSON bug fix.
52+
53+
## Exception Record
54+
55+
- Size exception:
56+
- scope: `lib/src/managedcode_tps.dart`
57+
- reason: the Flutter runtime is still implemented as one parity-first source file while the contract is stabilized across six runtimes
58+
- removal plan: split into `constants`, `parser`, `compiler`, `player`, `playback-session`, and `transport` files in the next maintainability pass

SDK/flutter/lib/src/managedcode_tps.dart

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ import "dart:math" as math;
44

55
enum TpsPlaybackStatus { idle, playing, paused, completed }
66

7+
abstract final class TpsPlaybackDefaults {
8+
static const defaultSpeedStepWpm = 10;
9+
static const defaultTickIntervalMs = 16;
10+
}
11+
12+
abstract final class TpsPlaybackEventNames {
13+
static const stateChanged = "stateChanged";
14+
static const wordChanged = "wordChanged";
15+
static const phraseChanged = "phraseChanged";
16+
static const blockChanged = "blockChanged";
17+
static const segmentChanged = "segmentChanged";
18+
static const statusChanged = "statusChanged";
19+
static const completed = "completed";
20+
static const snapshotChanged = "snapshotChanged";
21+
}
22+
723
class TpsPosition {
824
const TpsPosition({
925
required this.line,
@@ -1070,7 +1086,7 @@ class TpsPlayer {
10701086
class TpsPlaybackSession {
10711087
TpsPlaybackSession(CompiledScript scriptOrPlayer, [TpsPlaybackSessionOptions options = const TpsPlaybackSessionOptions()])
10721088
: player = TpsPlayer(scriptOrPlayer),
1073-
tickIntervalMs = options.tickIntervalMs ?? 16,
1089+
tickIntervalMs = options.tickIntervalMs ?? TpsPlaybackDefaults.defaultTickIntervalMs,
10741090
baseWpm = _normalizeBaseWpm(options.baseWpm),
10751091
speedStepWpm = _normalizeSpeedStep(options.speedStepWpm),
10761092
speedOffsetWpm = 0 {
@@ -1123,7 +1139,7 @@ class TpsPlaybackSession {
11231139
}
11241140

11251141
VoidCallback observeSnapshot(void Function(TpsPlaybackSnapshot) listener, [bool emitCurrent = true]) {
1126-
final unsubscribe = on("snapshotChanged", (event) => listener(event as TpsPlaybackSnapshot));
1142+
final unsubscribe = on(TpsPlaybackEventNames.snapshotChanged, (event) => listener(event as TpsPlaybackSnapshot));
11271143
if (emitCurrent) {
11281144
listener(snapshot);
11291145
}
@@ -1306,7 +1322,7 @@ class TpsPlaybackSession {
13061322
}
13071323
}
13081324

1309-
void _emitSnapshotChanged() => _emit("snapshotChanged", createSnapshot());
1325+
void _emitSnapshotChanged() => _emit(TpsPlaybackEventNames.snapshotChanged, createSnapshot());
13101326

13111327
int _readLiveElapsedMs() {
13121328
final deltaMs = ((_nowMs() - _playbackStartedAtMs) * playbackRate).round();
@@ -1346,7 +1362,7 @@ class TpsPlaybackSession {
13461362
if (nextStatus == TpsPlaybackStatus.completed && previousStatus == TpsPlaybackStatus.playing) {
13471363
_clearTimer();
13481364
}
1349-
_emit("statusChanged", {
1365+
_emit(TpsPlaybackEventNames.statusChanged, {
13501366
"state": currentState,
13511367
"previousStatus": previousStatus,
13521368
"status": nextStatus,
@@ -1363,22 +1379,22 @@ class TpsPlaybackSession {
13631379
currentState = nextState;
13641380
_updateStatus(resolvedStatus);
13651381
if (nextState.currentWord?.id != previousState.currentWord?.id) {
1366-
_emit("wordChanged", {"state": nextState, "previousState": previousState, "status": status});
1382+
_emit(TpsPlaybackEventNames.wordChanged, {"state": nextState, "previousState": previousState, "status": status});
13671383
}
13681384
if (nextState.currentPhrase?.id != previousState.currentPhrase?.id) {
1369-
_emit("phraseChanged", {"state": nextState, "previousState": previousState, "status": status});
1385+
_emit(TpsPlaybackEventNames.phraseChanged, {"state": nextState, "previousState": previousState, "status": status});
13701386
}
13711387
if (nextState.currentBlock?.id != previousState.currentBlock?.id) {
1372-
_emit("blockChanged", {"state": nextState, "previousState": previousState, "status": status});
1388+
_emit(TpsPlaybackEventNames.blockChanged, {"state": nextState, "previousState": previousState, "status": status});
13731389
}
13741390
if (nextState.currentSegment?.id != previousState.currentSegment?.id) {
1375-
_emit("segmentChanged", {"state": nextState, "previousState": previousState, "status": status});
1391+
_emit(TpsPlaybackEventNames.segmentChanged, {"state": nextState, "previousState": previousState, "status": status});
13761392
}
13771393
if (nextState.elapsedMs != previousState.elapsedMs || status != previousStatus) {
1378-
_emit("stateChanged", {"state": nextState, "previousState": previousState, "status": status});
1394+
_emit(TpsPlaybackEventNames.stateChanged, {"state": nextState, "previousState": previousState, "status": status});
13791395
}
13801396
if (!previousState.isComplete && resolvedStatus == TpsPlaybackStatus.completed) {
1381-
_emit("completed", {"state": nextState, "previousState": previousState, "status": status});
1397+
_emit(TpsPlaybackEventNames.completed, {"state": nextState, "previousState": previousState, "status": status});
13821398
}
13831399
_emitSnapshotChanged();
13841400
return nextState;
@@ -1462,7 +1478,7 @@ class TpsStandalonePlayer {
14621478
VoidCallback on(String eventName, void Function(Object?) listener) => session.on(eventName, listener);
14631479
void off(String eventName, void Function(Object?) listener) => session.off(eventName, listener);
14641480
VoidCallback observeSnapshot(void Function(TpsPlaybackSnapshot) listener, [bool emitCurrent = true]) => session.observeSnapshot(listener, emitCurrent);
1465-
VoidCallback onSnapshotChanged(void Function(TpsPlaybackSnapshot) listener) => session.on("snapshotChanged", (event) => listener(event as TpsPlaybackSnapshot));
1481+
VoidCallback onSnapshotChanged(void Function(TpsPlaybackSnapshot) listener) => session.on(TpsPlaybackEventNames.snapshotChanged, (event) => listener(event as TpsPlaybackSnapshot));
14661482
PlayerState play() => session.play();
14671483
PlayerState pause() => session.pause();
14681484
PlayerState stop() => session.stop();
@@ -3561,7 +3577,7 @@ bool _isSpokenWord(_WordSeed word) => word.kind == "word" && word.cleanText.isNo
35613577
int _clamp(int value, int minimum, int maximum) => math.min(math.max(value, minimum), maximum);
35623578
int _clampWpm(int candidate, int fallback) => candidate.isFinite ? _clamp(candidate, TpsSpec.minimumWpm, TpsSpec.maximumWpm) : fallback;
35633579
int _normalizeBaseWpm(int? value) => _clampWpm(value ?? TpsSpec.defaultBaseWpm, TpsSpec.defaultBaseWpm);
3564-
int _normalizeSpeedStep(int? value) => value == null || value <= 0 ? 10 : value;
3580+
int _normalizeSpeedStep(int? value) => value == null || value <= 0 ? TpsPlaybackDefaults.defaultSpeedStepWpm : value;
35653581
int _normalizeSpeedOffset(int baseWpm, int offset) => _clamp(baseWpm + offset, TpsSpec.minimumWpm, TpsSpec.maximumWpm) - baseWpm;
35663582
int _nowMs() => DateTime.now().millisecondsSinceEpoch;
35673583
TpsPlaybackStatus _resolveStatusAfterSeek(TpsPlaybackStatus current, int totalDurationMs, int elapsedMs) {

SDK/java/AGENTS.md

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,59 @@
1-
# ManagedCode.Tps Java SDK
1+
# AGENTS.md
2+
3+
Project: SDK Java runtime
4+
Owned by: ManagedCode.Tps SDK maintainers
5+
6+
Parent: `../../AGENTS.md`
7+
8+
## Purpose
9+
10+
- Own the standalone Java TPS runtime.
11+
- Keep Java compile, restore, playback, and snapshot behavior aligned with the shared TPS contract.
12+
13+
## Entry Points
14+
15+
- `src/main/java/com/managedcode/tps/ManagedCodeTps.java`
16+
- `src/test/java/com/managedcode/tps/ManagedCodeTpsTests.java`
17+
- `build.sh`
18+
- `test.sh`
19+
20+
## Boundaries
21+
22+
- In scope:
23+
- Java runtime implementation
24+
- Java runtime test harness and coverage script
25+
- parity with the shared fixture set
26+
- Out of scope:
27+
- Android UI integration
28+
- non-Java runtime source changes unless parity requires them
29+
30+
## Project Commands
31+
32+
- `build`: `./build.sh`
33+
- `test`: `./test.sh`
34+
- `coverage`: `./coverage.sh`
35+
36+
## Applicable Skills
37+
38+
- `mcaf-solid-maintainability`
39+
- `mcaf-testing`
40+
- `dotnet-mcaf-documentation`
41+
42+
## Local Risks Or Protected Areas
43+
44+
- Shared fixture parity with `SDK/fixtures/**` and `examples/*.tps` is a protected contract.
45+
- Public nested types under `ManagedCodeTps` are a stable host-facing API surface.
46+
- Playback snapshots and event names must remain transport-compatible with the other runtimes.
47+
48+
## Local Rules
249

3-
- Keep the Java runtime behavior aligned with `SDK/ts`, `SDK/js`, `SDK/dotnet`, `SDK/flutter`, and `SDK/swift`.
4-
- Validate against `SDK/fixtures/**` and `examples/*.tps`.
550
- Keep the runtime embeddable and UI-framework-neutral.
651
- Prefer immutable public models and deterministic playback math.
52+
- Replace meaningful literals with named constants before adding new playback behavior.
53+
54+
## Exception Record
55+
56+
- Size exception:
57+
- scope: `src/main/java/com/managedcode/tps/ManagedCodeTps.java`
58+
- reason: the Java runtime still ships as a parity-first single source file while the contract is stabilized
59+
- removal plan: split into package-level classes for constants, parser, compiler, player, playback session, and compiled transport

0 commit comments

Comments
 (0)