Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/badges/api-types.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 8 additions & 8 deletions .github/badges/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Change Log

## [0.9.9] - 2026-04-02 - Drawing Setters, NAMESPACES_LIKE Subscripts & force_overlay Sync

### Fixed

- **NAMESPACES_LIKE subscripts (transpiler)**: Subscripts on dual-use builtins (`time[1]`, `time_close[1]`, etc.) now emit **`$.get(name.__value, n)`** instead of **`name.__value[n]`**, so lookback matches Pine Script / forward-array Series semantics.
- **Box / line coordinate setters**: `set_lefttop`, `set_rightbottom`, `set_xy1`, `set_xy2`, `set_left`, `set_right`, and related setters on **box** and **line** helpers now call **`_resolve()`** so Series-derived coordinates unwrap the same way as **`new()`** constructors.
- **`force_overlay` on drawing objects**: **`syncToPlot()`** in **BoxHelper**, **LineHelper**, and **LabelHelper** routes **`force_overlay=true`** objects into **separate overlay plots** so chart integrations can place them on the main price pane.

### Added

- **Tests**: `box-setters-resolve`, namespace subscript transpiler coverage, and **gradient `fill()`** cases.

---

## [0.9.8] - 2026-03-27 - TA Cross/CrossUnder, Matrix·Vector, Plot Serialization & Input Fixes

### Fixed
Expand Down
6 changes: 3 additions & 3 deletions docs/api-coverage/pinescript-v6/types.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
"adjustment.splits": true
},
"alert": {
"alert.freq_all": false,
"alert.freq_once_per_bar": false,
"alert.freq_once_per_bar_close": false
"alert.freq_all": true,
"alert.freq_once_per_bar": true,
"alert.freq_once_per_bar_close": true
},
"backadjustment": {
"backadjustment.inherit": true,
Expand Down
6 changes: 3 additions & 3 deletions docs/api-coverage/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,9 @@ parent: API Coverage

| Function | Status | Description |
| ------------------------------- | ------ | ---------------------------------- |
| `alert.freq_all` | | Alert frequency all |
| `alert.freq_once_per_bar` | | Alert frequency once per bar |
| `alert.freq_once_per_bar_close` | | Alert frequency once per bar close |
| `alert.freq_all` | | Alert frequency all |
| `alert.freq_once_per_bar` | | Alert frequency once per bar |
| `alert.freq_once_per_bar_close` | | Alert frequency once per bar close |

### Backadjustment

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pinets",
"version": "0.9.8",
"version": "0.9.9",
"description": "Run Pine Script anywhere. PineTS is an open-source transpiler and runtime that brings Pine Script logic to Node.js and the browser with 1:1 syntax compatibility. Reliably write, port, and run indicators or strategies on your own infrastructure.",
"keywords": [
"Pine Script",
Expand Down
40 changes: 28 additions & 12 deletions src/namespaces/box/BoxHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,27 @@ export class BoxHelper {
public syncToPlot() {
this._ensurePlotsEntry();
const time = this.context.marketData[0]?.openTime || 0;
const allPlotData = this._boxes.map(bx => bx.toPlotData());

// Split force_overlay objects into a separate overlay plot (renders on main chart pane)
const regular = allPlotData.filter((b: any) => !b.force_overlay);
const overlay = allPlotData.filter((b: any) => b.force_overlay);

this.context.plots['__boxes__'].data = [{
time,
value: this._boxes.map(bx => bx.toPlotData()),
value: regular,
options: { style: 'drawing_box' },
}];

if (overlay.length > 0) {
this.context.plots['__boxes_overlay__'] = {
title: '__boxes_overlay__',
data: [{ time, value: overlay, options: { style: 'drawing_box' } }],
options: { style: 'drawing_box', overlay: true },
};
} else {
delete this.context.plots['__boxes_overlay__'];
}
}

private _resolvePoint(point: ChartPointObject): { x: number; xloc: string } {
Expand Down Expand Up @@ -172,32 +188,32 @@ export class BoxHelper {
// --- Coordinate setters ---

set_left(id: BoxObject, left: number): void {
if (id && !id._deleted) id.left = left;
if (id && !id._deleted) id.left = this._resolve(left);
}

set_right(id: BoxObject, right: number): void {
if (id && !id._deleted) id.right = right;
if (id && !id._deleted) id.right = this._resolve(right);
}

set_top(id: BoxObject, top: number): void {
if (id && !id._deleted) id.top = top;
if (id && !id._deleted) id.top = this._resolve(top);
}

set_bottom(id: BoxObject, bottom: number): void {
if (id && !id._deleted) id.bottom = bottom;
if (id && !id._deleted) id.bottom = this._resolve(bottom);
}

set_lefttop(id: BoxObject, left: number, top: number): void {
if (id && !id._deleted) {
id.left = left;
id.top = top;
id.left = this._resolve(left);
id.top = this._resolve(top);
}
}

set_rightbottom(id: BoxObject, right: number, bottom: number): void {
if (id && !id._deleted) {
id.right = right;
id.bottom = bottom;
id.right = this._resolve(right);
id.bottom = this._resolve(bottom);
}
}

Expand All @@ -221,9 +237,9 @@ export class BoxHelper {

set_xloc(id: BoxObject, left: number, right: number, xloc: string): void {
if (id && !id._deleted) {
id.left = left;
id.right = right;
id.xloc = xloc;
id.left = this._resolve(left);
id.right = this._resolve(right);
id.xloc = this._resolve(xloc);
}
}

Expand Down
23 changes: 17 additions & 6 deletions src/namespaces/label/LabelHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,28 @@ export class LabelHelper {

public syncToPlot() {
this._ensurePlotsEntry();
// Store ALL labels as a single array value at the first bar's time.
// Using a live reference so setter mutations are reflected automatically.
// Multiple label objects at the same bar would overwrite each other
// in QFChart's sparse data array, so we aggregate them into one entry
// (same approach as LineHelper._syncToPlot).
const time = this.context.marketData[0]?.openTime || 0;
const allPlotData = this._labels.map(lbl => lbl.toPlotData());

// Split force_overlay objects into a separate overlay plot (renders on main chart pane)
const regular = allPlotData.filter((l: any) => !l.force_overlay);
const overlay = allPlotData.filter((l: any) => l.force_overlay);

this.context.plots['__labels__'].data = [{
time,
value: this._labels.map(lbl => lbl.toPlotData()),
value: regular,
options: { style: 'label' },
}];

if (overlay.length > 0) {
this.context.plots['__labels_overlay__'] = {
title: '__labels_overlay__',
data: [{ time, value: overlay, options: { style: 'label' } }],
options: { style: 'label', overlay: true },
};
} else {
delete this.context.plots['__labels_overlay__'];
}
}

/**
Expand Down
38 changes: 25 additions & 13 deletions src/namespaces/line/LineHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,28 @@ export class LineHelper {

public syncToPlot() {
this._ensurePlotsEntry();
// Store ALL lines as a single array value at the first bar's time.
// Using a live reference so setter mutations are reflected automatically.
// Multiple drawing objects at the same bar would overwrite each other
// in QFChart's sparse data array, so we aggregate them into one entry.
const time = this.context.marketData[0]?.openTime || 0;
const allPlotData = this._lines.map(ln => ln.toPlotData());

// Split force_overlay objects into a separate overlay plot (renders on main chart pane)
const regular = allPlotData.filter((l: any) => !l.force_overlay);
const overlay = allPlotData.filter((l: any) => l.force_overlay);

this.context.plots['__lines__'].data = [{
time,
value: this._lines.map(ln => ln.toPlotData()),
value: regular,
options: { style: 'drawing_line' },
}];

if (overlay.length > 0) {
this.context.plots['__lines_overlay__'] = {
title: '__lines_overlay__',
data: [{ time, value: overlay, options: { style: 'drawing_line' } }],
options: { style: 'drawing_line', overlay: true },
};
} else {
delete this.context.plots['__lines_overlay__'];
}
}

private _resolvePoint(point: ChartPointObject): { x: number; xloc: string } {
Expand Down Expand Up @@ -161,32 +173,32 @@ export class LineHelper {
// --- Setter methods ---

set_x1(id: LineObject, x: number): void {
if (id && !id._deleted) id.x1 = x;
if (id && !id._deleted) id.x1 = this._resolve(x);
}

set_y1(id: LineObject, y: number): void {
if (id && !id._deleted) id.y1 = y;
if (id && !id._deleted) id.y1 = this._resolve(y);
}

set_x2(id: LineObject, x: number): void {
if (id && !id._deleted) id.x2 = x;
if (id && !id._deleted) id.x2 = this._resolve(x);
}

set_y2(id: LineObject, y: number): void {
if (id && !id._deleted) id.y2 = y;
if (id && !id._deleted) id.y2 = this._resolve(y);
}

set_xy1(id: LineObject, x: number, y: number): void {
if (id && !id._deleted) {
id.x1 = x;
id.y1 = y;
id.x1 = this._resolve(x);
id.y1 = this._resolve(y);
}
}

set_xy2(id: LineObject, x: number, y: number): void {
if (id && !id._deleted) {
id.x2 = x;
id.y2 = y;
id.x2 = this._resolve(x);
id.y2 = this._resolve(y);
}
}

Expand Down
17 changes: 17 additions & 0 deletions src/transpiler/transformers/ExpressionTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,23 @@ function transformOperand(node: any, scopeManager: ScopeManager, namespace: stri
NAMESPACES_LIKE.includes(node.object.name) &&
scopeManager.isContextBound(node.object.name);

// For computed access on NAMESPACES_LIKE identifiers (e.g. time[1], close[2]),
// produce $.get(identifier.__value, offset) instead of identifier.__value[offset].
const isNamespaceSubscript = node.computed &&
node.object.type === 'Identifier' &&
NAMESPACES_LIKE.includes(node.object.name) &&
scopeManager.isContextBound(node.object.name);

if (isNamespaceSubscript) {
const valueExpr = {
type: 'MemberExpression',
object: { type: 'Identifier', name: node.object.name },
property: { type: 'Identifier', name: '__value' },
computed: false,
};
return ASTFactory.createGetCall(valueExpr, node.property);
}

// Handle array access
const transformedObject = (node.object.type === 'Identifier' && !isNamespacePropAccess)
? transformIdentifierForParam(node.object, scopeManager)
Expand Down
Loading
Loading