# fixes 0.12.0 (2026-07-05)

## Bug fixes

- **`run_did()` reported `nrow(data)` instead of the estimation sample size.**
  `attr(., "N")` and `N_treated` (and the printed "N = … obs") counted all
  rows of `data`, including those `fixest::feols()` drops for missing values,
  disagreeing with `nobs(model)`, `broom::glance()`, and `run_es()` (which
  already report the fitted sample). `N`, `N_units`, and `N_treated` now
  describe the estimation sample, recovered from the model's `obs_selection`.
  On data without missing values the numbers are unchanged.

## New features

- **`calc_att()` results can now be tabulated.** `att_result` gains
  `broom::tidy()` and `broom::glance()` methods, so aggregated ATTs (overall,
  by-cohort, or by-time; CS or BJS) can be passed to
  `modelsummary::modelsummary()` and rendered with `tinytable`. Several
  estimators — e.g. `run_did()` TWFE, CS, and BJS — can be stacked side by side
  in one table. The `term` column is derived from the aggregation type
  (`"ATT"`, `"Cohort <g>"`, `"Time <t>"`) so rows align across estimators.

- **`run_es(..., rel_time = <col>)`**: pass a pre-built event-time column (time
  relative to treatment) — e.g. the `Time_to_Treatment` from
  `paneltools`/`fect`'s `get.cohort()` — and `run_es()` uses it verbatim as the
  event-study factor (`i(rel_time, treatment, ref = baseline)`) instead of
  computing relative time from `time`/`timing`. This normalises the previous
  `time = event_time, timing = 0` idiom and, unlike that idiom, keeps
  never-treated controls (`NA` rel_time) in the estimation sample. Requires
  `estimator = "twfe"` and `method = "classic"`; the default calendar-difference
  behaviour is unchanged. See the new **Details** section of `?run_es` for the
  three relative-time conventions (calendar difference, within-unit rank,
  pre-built) and when they diverge in sparse/unbalanced panels.

## Diagnostics

- **CS** now names cohorts that are silently dropped because their base period
  `g-1` is not observed (a real hazard in sparse panels, where a cohort can
  vanish from the by-cohort and event-study aggregates), and exposes them as
  `attr(result, "dropped_cohorts")` on both `run_es()` and `calc_att()`.
- **SA** warns when the saturated design is degenerate (a large share of
  cohort×period interactions dropped as collinear, or implausibly large CATT
  standard errors), which is easy to miss with sparse/irregular time.
- **BJS** now reports `attr(result, "n_unimputed")` (and `n_treated_obs`): the
  number of treated observations whose counterfactual could not be imputed and
  were excluded from aggregation, on both `run_es()` and `calc_att()`.

## Internals — reduced `fixest` dependency

The long-term plan of trimming the `fixest` dependency (see `CLAUDE.md`)
begins in this release. Two internal engines now replace `fixest::feols()`
in most estimators; **all user-facing APIs, output columns, and numerics are
unchanged** — every change here is verified against the previous
`fixest`-based results to numerical agreement (`tolerance = 1e-6`, typically
much closer).

- **BJS Step 1** (fixed-effects nuisance regression) is now estimated by an
  internal alternating-projections solver (`src/fe_solver.cpp`) instead of
  `fixest::feols(y ~ 0 | unit + time)` + `fixest::fixef()`. It reproduces
  `fixest`'s estimation sample exactly, including the iterative removal of
  singleton fixed-effect levels (dropping one singleton can create another).
- **SA, TWM, FLEX, and `compute_contamination_weights()`** now run their
  saturated cohort × period regressions through a new internal FE-OLS engine
  (`R/fe_ols.R`, `src/demean_kway.cpp`, `src/fe_ols_kernels.cpp`): k-way
  fixed-effects demeaning, keep-first collinearity dropping, and iid /
  heteroskedasticity-robust (HC1) / one-way cluster-robust standard errors
  that reproduce `fixest`'s default small-sample corrections exactly
  (`ssc(adj = TRUE, cluster.adj = TRUE)`, nested-fixed-effect accounting).
  Requests the internal engine cannot reproduce exactly — multi-way
  clustering, custom `vcov_args`, or fixed-effect specifications that are not
  plain columns — are transparently delegated to `fixest`, so every code
  path keeps returning identical results.
- The regressions are parallelised with OpenMP (falls back to sequential
  execution where OpenMP is unavailable) and are within a few percent of
  `fixest`'s multi-threaded speed on saturated designs with hundreds of
  cohort × period columns. Thread count defaults to half the detected
  physical cores and can be set with `options(fixes.nthreads = <n>)`.
- `fixest` remains the estimation engine for classic TWFE / `method =
  "sunab"` in `run_es()`, for `run_did()`, and as the fallback described
  above; it is still a required (`Imports`) dependency. `parallel` (base R)
  is now also imported, for core-count detection only.

# fixes 0.11.2 (2026-06-11)

## Bug fixes (silent-corruption class)

- **CS estimator: character / factor unit IDs produced silently wrong results**
  (`run_es(estimator = "cs")` and `calc_att(estimator = "cs")`): unit IDs were
  coerced with `as.integer()`, which turns every character ID into `NA` — all
  units collapsed into one inside the Rcpp kernel and the ATT(g,t) table was
  meaningless, with no error raised. Unit IDs are now densely re-encoded with
  `match()`; character, factor, and arbitrary numeric IDs give results
  identical to integer IDs.

- **CS estimator: a cohort with `timing == 0` leaked into the control group**:
  `0L` was the internal never-treated sentinel, so a genuine cohort first
  treated at time 0 (e.g., event-time-centred data) was silently used as
  never-treated controls. `time`/`timing` are now shifted by a common internal
  offset so the sentinel can never collide; results are invariant to common
  shifts of the time axis.

- **Classic staggered TWFE dropped the entire never-treated control group**
  (`estimator = "twfe"`, `staggered = TRUE`): the relative-time variable is
  `NA` for never-treated units and `fixest::feols()` removes rows with NA
  regressors, so every control row was excluded from the estimation sample
  (visible only as a fixest NOTE). Those rows are now assigned the (excluded)
  baseline level — their event dummies are identically zero because the `i()`
  interaction is multiplied by `treatment = 0` — so the control group is
  correctly retained. `N_treated` / `N_nevertreated` metadata still reflect
  the original NA pattern.

- **`method = "sunab"` dropped the entire never-treated control group**:
  `fixest::sunab()` has no NA-cohort convention and drops those rows.
  Never-treated units are now recoded internally to a cohort far beyond the
  sample period (the `fixest::base_stagg` convention) and retained as
  controls. Verified numerically identical to a direct
  `fixest::feols(y ~ sunab(...))` call using the same recoding.

- **Rcpp (unit, time) lookup key**: replaced the multiplicative hash (which
  could collide for time values above 1,000,003, e.g. Unix timestamps) with a
  collision-free 64-bit `(unit << 32) | time` key in
  `src/compute_att_gt.cpp`.

## Improved input validation

- CS: clear, actionable error when the time grid does not advance in steps of
  1 (the `g - 1` base-period assumption), instead of the generic
  "No ATT(g,t) estimates were computed".
- CS: warning that names cohorts skipped because their base period
  (`g - 1 - anticipation`) is not observed.
- CS / SA / TWM / FLEX: fractional `time` / `timing` values now raise an error
  instead of being silently truncated by `as.integer()` (new shared helper
  `.assert_integerish()`).
- SA / TWM / FLEX: warning when the requested `baseline` reference period is
  not feasible for any cohort, in which case no period would be excluded and
  the regression is unnormalised.

## Performance

- Verified on a 400,000-row staggered panel (20,000 units × 20 periods):
  every estimator completes in roughly 1–3 s (CS bootstrap with B = 999
  ≈ 3 s), peak memory ≈ 1.3 GB.
- BJS: singleton-unit FE recovery vectorised (was O(#singletons × N), which
  could take minutes on large unbalanced panels).
- CS: event-study aggregation and bootstrap influence-function bookkeeping
  vectorised (`match()` on keys instead of per-cohort scans).

## Tests

- New `tests/testthat/test-robustness.R` (19 assertions) covering all of the
  above: ID-type invariance, time-shift invariance, control-group retention
  (classic TWFE and sunab vs. direct fixest reference), grid-spacing errors,
  fractional-time rejection, and BJS singleton imputation.

---

# fixes 0.11.1 (2026-05-30)

## Improvements

- **`run_es()` — diagnostic warning for ambiguous `timing = NA`** (classic TWFE,
  `staggered = TRUE` only): a `warning()` is now emitted when any unit has
  `treatment = 1` in at least one row but `timing = NA`. Such units are silently
  absorbed as never-treated controls — correct when `NA` is intentional, but
  dangerous when `NA` represents genuinely missing treatment timing. The warning
  names up to 5 affected unit IDs (when `unit` is supplied) for quick diagnosis.

- **`@param timing` documentation clarified**: all staggered estimators (cs, sa,
  bjs, twm, flex, classic with `staggered = TRUE`) share the same `NA = never
  treated` convention as `did::att_gt()` and `fixest::sunab()`. The roxygen
  documentation now states this explicitly and notes the risk of silent
  misclassification when `NA` represents missing data rather than "never treated".

---

# fixes 0.11.0 (2026-05-24)

## New Features

- **`honest_sensitivity()` — honest robust inference (Rambachan & Roth 2023)**
  ([R/honest_did.R](R/honest_did.R)): sensitivity analysis for event-study / DiD
  designs when parallel trends may be violated. Instead of assuming parallel
  trends holds exactly, it reports confidence sets for a post-treatment effect
  under progressively weaker restrictions on the difference in trends, and a
  *breakdown value* (the largest restriction at which the effect is still
  significant):

  ```r
  res <- run_es(df, outcome = y, treatment = treat, time = year, timing = 6,
                fe = ~ id + year)
  h <- honest_sensitivity(res, type = "relative_magnitude",
                          Mvec = c(0, 0.5, 1, 1.5, 2))
  plot_honest(h)
  ```

  - Restriction families: `"relative_magnitude"` (\eqn{\Delta^{RM}(\bar M)}) and
    `"smoothness"` (\eqn{\Delta^{SD}(M)}).
  - Inference via the Andrews-Roth-Pakes (ARP) **conditional** moment-inequality
    test — a pure-R reimplementation (no dependency on the HonestDiD package).
  - Works directly from a `run_es()` `es_result` (estimator `"twfe"`: classic or
    `method = "sunab"`, which now carry the event-study coefficient covariance
    via the new `es_vcov` attribute), or from a raw `betahat` / `sigma` pair for
    any estimator.
  - `plot_honest()` / `autoplot()` draw the "top-down" sensitivity plot;
    `print.honest_result()` reports the breakdown value.
  - Validated against the **HonestDiD** reference package (method
    `"Conditional"`): \eqn{\Delta^{SD}} (single post period) matches to machine
    precision; \eqn{\Delta^{RM}} matches to grid resolution. Heavy numeric
    helpers (`lpSolveAPI`, `Rglpk`, `TruncatedNormal`, `Matrix`, `pracma`) are in
    `Suggests` and gated with `requireNamespace()`.

## Internal / Refactor

- **Estimator-core consolidation** (behaviour-preserving): factored the
  boilerplate duplicated across `run_es()`, `calc_att()`, `run_did()`, and the
  five estimator backends into shared helpers in
  [R/utils-internal.R](R/utils-internal.R) — `.resolve_col()` (NSE column
  resolution), `.add_ci_columns()` (CI construction), `.validate_panel_cols()`,
  `.compute_cohort_sizes()`, `.model_vcov_full()`, and `.aggregate_iw()`. The
  `calc_att()` aggregation loops were vectorised. All existing tests pass
  unchanged.

- **`run_did()` — basic TWFE DiD estimator** ([R/run_did.R](R/run_did.R)):
  New function for classic two-way fixed-effects difference-in-differences.
  Accepts a pre-built binary treatment indicator `D_it` and returns a
  `did_result` S3 object with full `modelsummary` / `tinytable` compatibility:

  ```r
  df$D <- as.integer(df$treated & df$year >= 2006)
  result <- run_did(df, outcome = y, treatment = D, fe = ~ id + year)
  print(result)
  modelsummary::modelsummary(result)   # tinytable output (modelsummary >= 2.0)
  ```

  - NSE API consistent with `run_es()` and `calc_att()` — specify variables
    by name, not by formula construction.
  - `fe` auto-inferred as `~ unit + time` when both `unit` and `time` are
    supplied and `fe = NULL`.
  - `tidy.did_result()` delegates to `broom::tidy.fixest()` for full
    regression table (all regressors); `glance.did_result()` delegates to
    `broom::glance.fixest()` for model-level stats (`within.r.squared`,
    `nobs`, `AIC`, etc.).
  - `print.did_result()` shows a clean header: estimator, sample sizes,
    FE spec, VCOV type.
  - Outcome expressions like `log(y)` supported.
  - `modelsummary` and `tinytable` added to Suggests.

- **`calc_att()` — ATT aggregation function** ([R/calc_att.R](R/calc_att.R)): New
  function that separates "calculating ATT" from "running an event study".
  While `run_es()` produces a full dynamic event-study curve (effects by
  relative time), `calc_att()` yields aggregated ATT estimates with a clear,
  minimal interface:

  ```r
  calc_att(data, outcome = y, time = year, timing = g, unit = id,
           estimator = "cs",
           aggregation = "simple")     # overall ATT
  calc_att(..., aggregation = "by_cohort")  # ATT per cohort
  calc_att(..., aggregation = "by_time")    # ATT per calendar period
  ```

  - **Estimators**: `"cs"` (Callaway-Sant'Anna 2021) and `"bjs"` (Borusyak
    et al. 2024). SA, TWM, FLEX are event-study-only estimators and are not
    supported (use `run_es()`).
  - **Aggregation formulas (CS)** match `did::aggte()` for point estimates:
    - `"simple"`: exposure-weighted average over all post-treatment (g,t) pairs.
    - `"by_cohort"`: mean ATT(g,t) over post-treatment periods per cohort.
    - `"by_time"`: cohort-size-weighted ATT per calendar period.
  - **SEs**: independence assumption (diagonal delta-method). Systematically
    smaller than `did::aggte` SEs, which use influence-function variance
    accounting for within-cohort time correlation. Cluster-robust SE for BJS
    aggregations planned for v0.12.
  - **Returns** an `att_result` S3 object (data.frame subclass) with columns
    `group`, `estimate`, `std.error`, `statistic`, `p.value`,
    `conf_low_XX`/`conf_high_XX`.  Attributes: `aggregation`, `estimator`,
    `N`, `N_units`, `N_treated`, `control_group` (CS only), `att_gt` (raw
    CS ATT(g,t) table), `tau_it` (BJS unit-time effects table).
  - **`print.att_result()`** S3 method provides a concise summary.

## Testing

- **48 new tests** in `tests/testthat/test-att.R`:
  - Return type and column contract for all aggregation types
  - CS point estimates match `did::aggte()` at tolerance 1e-4
  - BJS estimates match manual `mean(tau_it$tau_hat)` at tolerance 1e-12
  - Multiple `conf.level` support verified

---

# fixes 0.10.1 (2026-05-14)

## Internal

- **`run_es.R` refactoring**: Extracted `.make_tidy()` and `.es_finalize()` private
  helpers that centralise the repeated tidy-construction / baseline-row / range-filter /
  attr-stamping pattern that was previously duplicated across the `cs`, `sa`, `bjs`,
  `twm`, and `flex` branches (~360 lines removed).  No API change; all 593 tests pass.
- **profvis benchmark harness**: Added `inst/profile/benchmark.R` with `profile_estimator()`
  and `benchmark_all()` utilities.  Run interactively to generate flamegraph HTML and
  wall-clock summary across estimators before starting Phase 3 Rcpp work.
- **CLAUDE.md documentation**: Added `src/compute_att_gt.cpp` (shipped in v0.8.1 but
  missing from the Rcpp Acceleration Roadmap), and updated Package Structure to list all
  13 R source files, 5 Rcpp files, and 16 test files that now exist.
- **Edge-case tests** (19 new, 593 total):
  - `test-twm.R` Test 16 — `trends=TRUE` with exactly 2 pre-treatment periods succeeds
    without warning (boundary condition of the ≥ 2 requirement).
  - `test-twm.R` Test 17 — explicit `lead_range`/`lag_range` correctly trims TWM output
    and preserves estimates in the shared window (exercises `.es_finalize()` filter).
  - `test-flex.R` Test 12 — explicit `lead_range`/`lag_range` trims FLEX output correctly.
  - `test-flex.R` Test 13 — FLEX succeeds with an all-treated panel (no never-treated
    groups; `N_nevertreated == 0`).
  - `test-sa.R` — explicit `lead_range`/`lag_range` trims SA output and preserves
    estimates within the shared window.

---

# fixes 0.10.0 (2026-05-14)

## New Features
- `run_es(estimator = "twm", trends = TRUE)`: Cohort-specific linear trend
  detrending (Wooldridge 2025, Section 8). Adds `d_g * t` regressors so each
  cohort's counterfactual trend can deviate linearly from the common time
  trend. Implemented as post-treatment-only cells + trend columns; requires
  >= 2 pre-treatment periods per cohort. Output shows `relative_time >= 0`
  only (pre-trend testing is deferred to the no-trend model).
- `run_es(estimator = "twm", covariates = ~ x1 + x2)`: Full Wooldridge (2025)
  Procedure 5.1 covariate interactions. Adds treatment-cell × cohort-demeaned
  covariate terms (`ẋ_{ig} = x_i - x̄_g`) and `i(time, x_j)` conditional
  parallel-trends controls (Eqs. 5.2-5.3).
- `run_es(estimator = "flex", covariates = ~ x1 + x2)`: Full Deb et al. (2024)
  Eq. 3.1 covariate interactions for RCS data. Cell-level centering
  `X_{i,t} - X̄_{g,t}` (Eq. 2.11) with `i(time, x_j)` and `i(group, x_j)`
  conditional PT controls.

## Performance (Rcpp Phase 2)
- `src/indicator_matrix.cpp` — `build_indicator_matrix_cpp()`: Replaces the
  pure-R nested for-loop that fills the 0/1 indicator matrix in the SA, TWM,
  and FLEX estimators. Single shared Rcpp utility, O(N×K) integer fill with
  no R allocation overhead per column.
- `src/iw_aggregation.cpp` — `aggregate_iw_cpp()`: Replaces the R event-time
  aggregation loop and the `t(w) %*% V_sub %*% w` quadratic-form VCOV step in
  SA, TWM, and FLEX. Uses RcppArmadillo `arma::mat::submat()` for O(1) VCOV
  block extraction and `arma::as_scalar(w.t() * V_sub * w)` for the quadratic
  form.
- `src/cov_demeaning.cpp` — `build_cov_interactions_cpp()`: Replaces the
  pure-R covariate demeaning loops in TWM (cohort-level centering) and FLEX
  (group×time cell-level centering) with a single `std::unordered_map`-based
  O(N) grouping pass followed by column-wise mean subtraction, then builds the
  N × (K×p) treatment-cell × centred-covariate interaction matrix in C++.
- `src/bootstrap_cs.cpp` — `bootstrap_cs_cpp()`: Replaces the R-level
  `apply`/`sweep` calls in the CS multiplier bootstrap with RcppArmadillo DGEMM
  for both the pilot and main stages. Uses `R::runif()` for Mammen weight
  generation (column-major fill order matches R's `matrix()`) so results are
  numerically identical to the pure-R path under the same `set.seed()`.
- Added `src/Makevars` and `src/Makevars.win` linking `$(LAPACK_LIBS)
  $(BLAS_LIBS) $(FLIBS)` for cross-platform RcppArmadillo support.
- `RcppArmadillo` added to `LinkingTo` in DESCRIPTION.

## Internal
- 574 tests passing (47 new over v0.9.0: trends 4, TWM-covariates 3,
  FLEX-covariates 3, Rcpp-indicator 4, Rcpp-IW 3, Rcpp-covdemeaning 6,
  Rcpp-bootstrap 12, plus sub-assertions).

---

# fixes 0.9.0 (2026-05-14)

## New Features
- `run_es(estimator = "twm")`: Wooldridge (2025) Two-Way
  Mundlak (TWM) estimator for panel data. Implements
  Procedure 5.1 via POLS on cohort x calendar-time
  treatment cells with two-way FE. Algebraically
  identical to `estimator = "sa"` in the no-covariate
  base case (verified numerically at tolerance 1e-10).
  The `trends = TRUE` extension (cohort-specific linear
  trend detrending, Wooldridge 2025 Section 8) is
  reserved for v0.9.1.
- `run_es(estimator = "flex", group = <group_id>)`:
  Deb, Norton, Wooldridge & Zabel (2024) FLEX estimator
  for **repeated cross-section (RCS)** data. Uses group
  x calendar-time OLS with group+time FE (no unit FE).
  Algebraically equivalent to the multi-step imputation
  estimator (Proposition 2.1). New `group` argument
  identifies the treatment group (R_{ig}) each
  individual belongs to. Default clustering is by group.
  Covariate interactions deferred to v0.9.1.
- Reference PDFs converted to TXT:
  `papers/Deb et al. (2024).txt`,
  `papers/Woodridge (2025).txt`.

## Internal
- 517 tests passing (41 new: TWM 21, FLEX 20).
- Both new estimators: pre-allocated integer indicator
  matrix construction (same strategy as SA optimisation)
  + feols() + quadratic-form VCOV. No new dependencies.

---

# fixes 0.8.1 (2026-05-10)

## New Features
- `run_es(estimator = "cs")`: Callaway-Sant'Anna (2021)
  estimator. Computes group-time ATT(g,t) via the
  unconditional DiD estimand (eq. 2.8) with
  never-treated or not-yet-treated control groups.
  Supports `control_group = "nevertreated"` (default)
  or `"notyettreated"`. The full ATT(g,t) matrix is
  stored as the `att_gt` attribute of the result.
- `run_es(estimator = "sa")`: Sun-Abraham (2021)
  interaction-weighted estimator. Aggregates cohort x
  relative-time interactions by cohort share weights.
  Numerically identical to `fixest::sunab()` to
  machine precision.
- `run_es(estimator = "bjs")`: Borusyak, Jaravel &
  Spiess (2024) imputation estimator. Fits TWFE on
  untreated observations only, imputes counterfactuals
  for treated observations, and averages treatment
  effects by horizon. Handles singleton unit fixed
  effects via closed-form recovery.
- `run_es(bootstrap = TRUE)`: Multiplier bootstrap for
  simultaneous confidence bands (Algorithm 1, Callaway
  & Sant'Anna 2021). Adds `conf_low_sim` /
  `conf_high_sim` columns to the result and stores the
  (g,t)-level bootstrap object as
  `attr(result, "bootstrap")`. Controlled by `B`
  (draws, default 999) and `boot_seed` arguments.
  Available only when `estimator = "cs"`.
- `plot_att_gt()`: Visualize the full ATT(g,t) matrix
  from CS results as a heatmap (`type = "heatmap"`) or
  cohort-faceted time series (`type = "facet"`).
  Requires `estimator = "cs"` result as input.
  When `bootstrap = TRUE` was used, the heatmap adds
  a subtitle with the simultaneous critical value and
  open-diamond markers for simultaneously significant
  cells; the facet adds a lighter simultaneous CI
  ribbon.
- `plot_es(show_simultaneous = TRUE)`: Overlays the
  simultaneous bootstrap CI (lighter band, alpha 0.15)
  alongside the pointwise CI (alpha 0.3) with a
  two-entry legend. Errors informatively if bootstrap
  was not run.
- `plot_es_interactive(show_simultaneous = TRUE)`:
  Adds a second lighter ribbon trace for the
  simultaneous CI and extends the hover tooltip with
  simultaneous CI bounds.

## Bug Fixes
- `run_es()` bootstrap path: `base::merge()` silently
  dropped all custom attributes (e.g. `att_gt`,
  `N_units`, `lead_range`) from the `es_result` object.
  Attributes are now saved and restored around the
  merge, so `plot_att_gt()` works correctly after a
  `bootstrap = TRUE` run.

## Internal
- Added `did` and `didimputation` to Suggests for
  numerical agreement tests against reference
  implementations.
- 195 tests passing across all estimators,
  visualization functions, and bootstrap routines.

---

# fixes 0.7.2 (2026-05-02)

## Bug Fixes
- **Clustered standard errors no longer silently overridden by HC1:**
  - Fixed a bug where specifying `cluster` in `run_es()` did not produce clustered standard errors
  - The default `vcov = "HC1"` was being applied after model estimation, overriding the clustered SE from `feols()`
  - Now, when `cluster` is specified and `vcov` is left at its default (`"HC1"`), the model's clustered standard errors are used
  - To explicitly request HC1 SEs even when `cluster` is set, pass `vcov = "HC1"` together with `cluster = NULL`
  - Applies to both `classic` and `sunab` methods
  - This resolves discrepancies between `run_es()` results and equivalent direct `fixest::feols()` calls

---

# fixes 0.7.1 (2025-11-02)

## Bug Fixes
- **Lead/lag range filtering now enforced:**
  - Fixed bug where `lead_range` and `lag_range` parameters were ignored, causing all estimated coefficients to be returned regardless of specified ranges
  - Results now correctly filtered to only include coefficients within `[-lead_range, lag_range]`
  - Applies to both `classic` and `sunab` methods

- **sunab() namespace error resolved:**
  - Fixed `'sunab' is not an exported object from 'namespace:fixest'` error when using `method = "sunab"`
  - Formula environment now correctly configured to access `sunab` function from fixest package
  - sunab() calls now work identically to direct fixest::feols() usage

## Enhancements
- **Baseline row now included in sunab results:**
  - The `baseline` parameter (default: -1) now applies to both `classic` and `sunab` methods
  - Baseline period added to results with `estimate = 0`, `std.error = 0`, and `is_baseline = TRUE`
  - Provides consistent behavior across both estimation methods
  - Updated documentation to clarify that `baseline` is used for both methods

- **Improved term column formatting:**
  - Term values now display as clean numeric strings (e.g., `"-9"`, `"0"`, `"3"`)
  - Removed complex fixest-specific notation (e.g., `"year::-9"`) for better readability
  - Consistent formatting across both classic and sunab methods

- **Modernized code with native pipe operator:**
  - Replaced tidyverse pipe (`%>%`) with base R pipe (`|>`) throughout the package
  - Requires R >= 4.1.0 (already enforced in DESCRIPTION)
  - Cleaner code with no functional changes

## Testing
- **Comprehensive test suite overhaul:**
  - Added 25+ test cases covering all bug fixes and enhancements
  - Separate test suites for classic, staggered, and sunab methods
  - Tests for baseline row inclusion, term formatting, and range filtering
  - Integration tests with fixest built-in datasets (base_did, base_stagg)
  - Tests use native pipe operator (`|>`)

## Compatibility
- Fully backward compatible with previous versions
- All changes maintain existing API and functionality
- Enhanced robustness and user experience

---

# fixes 0.7.0 (2025-11-01)

## Bug Fixes
- **Critical fix for relative_time calculation in non-staggered designs:**
  - Fixed bug where `relative_time` was NA for all coefficients except baseline when using non-staggered treatment timing
  - The reference period in `fixest::i()` now correctly corresponds to the `baseline` parameter
  - For example, with `timing = 5` and `baseline = -1`, period 4 (not period 5) is now used as the reference
  - This ensures proper event study estimation with the intended baseline period

## New Features
- **Interactive plotting with plotly:**
  - New function `plot_es_interactive()` for creating interactive event study plots
  - Hover tooltips display: relative time, point estimate, confidence intervals, standard error, and p-value
  - Supports customizable confidence levels, colors, and styling options
  - Optional ribbon or point-only display modes
  - Fully compatible with `es_result` objects from `run_es()`

## Performance Improvements
- **Optimized relative_time extraction:**
  - Vectorized term parsing replaces previous `sapply()` approach for significant performance gains
  - Faster processing of large event study results
- **Enhanced error reporting:**
  - More informative warnings when term parsing fails
  - Better debugging information for malformed coefficient names

## Compatibility
- Fully backward compatible with previous versions
- Requires R >= 4.1.0
- Interactive plotting requires `plotly` package (optional, suggested dependency)

---

# fixes 0.5.0 (2025-07-06)

## New Features
- **Multiple Confidence Intervals:**  
  `run_es()` and `plot_es()` now support returning and visualizing multiple confidence levels (e.g., 90%, 95%, 99%) in a single analysis.
- **Enhanced Plotting Options:**  
  `plot_es()` adds `ci_level` selection, more theme options (`theme_style`), and improved ribbon/error bar display.
- **Expanded Error Handling:**  
  More informative error reporting for all argument validation and model mis-specification.
- **Documentation Overhaul:**  
  Updated README, vignette, and roxygen docs to reflect all new features and usage patterns.

## Improvements
- **Input Validation:**  
  Stricter checks for all arguments and clearer, friendlier error messages.
- **Staggered Timing:**  
  More robust handling of partial or missing treatment timing.
- **Code Refactoring:**  
  Internal codebase cleaned for maintainability and reliability.

## Bug Fixes
- Fixed: Confidence intervals always correctly labeled and included in output.
- Fixed: Dummy variables not created for untreated/never-treated units in staggered settings.
- Fixed: CRAN warnings by declaring all global variables used in tidy evaluation.

## Compatibility
- Fully backward compatible with previous versions.
- Minimum required R version remains 4.1.0.

# fixes 0.4.1 (2025-06-30)

## New Features
- **Support for untreated units (`NA` in timing):**
  - Units with missing `timing` are now retained as never-treated controls (staggered only).
- **Flexible `weights` input:**
  - Accepts formulas (`~ popwt`), bare names (`popwt`), or character strings (`"popwt"`).
- **Improved `cluster` input handling:**
  - Now supports formula, character vector, and bare names consistently.

## Improvements
- Better validation and error messages across all inputs.
- Warning added if `unit` is supplied without `time_transform = TRUE`.
- Documentation switched to Markdown-style lists for better readability.

## Bug Fixes
- Ensured untreated units don’t receive event dummies.
- Improved internal dummy creation and baseline handling.

## Compatibility
- Fully backward compatible with previous versions.

# fixes 0.4.0 (May 25, 2025)

## New Features
- Added support for **staggered treatment timing**:
  - New `staggered = TRUE` option allows `timing` to vary by unit (e.g., treatment year column).
  - Units with `NA` in `timing` are safely retained as untreated.

- Added support for **observation weights**:
  - Use the `weights` argument (e.g., `~ popwt`) to run weighted regressions.

- **Automatic lead/lag range detection**:
  - If `lead_range` or `lag_range` is `NULL`, the function computes the maximum feasible range from the data.

## Improvements
- Safer handling of collinearity and untreated units.
- Warning is issued when `unit` is specified without `time_transform = TRUE`.
- Input validation and internal structure cleaned up for better robustness.

## Compatibility
- Backward compatible with previous versions.


# fixes 0.3.1 (May 18, 2025)

## Improvements
- Improved default x-axis scaling in `plot_es()`:
  - The x-axis now uses `ggplot2::scale_x_continuous()` with integer breaks spaced by 1, aligned to the `relative_time` range.
  - This provides more readable and consistent event time labeling in plots.

# fixes 0.3.0 (May 18, 2025)

## New Features
- Added support for irregular and non-numeric time variables such as `Date`:
  - Introduced `time_transform = TRUE` to automatically convert the `time` variable into a unit-level sequential index (1, 2, 3, ...) for event study estimation.
  - Added `unit` argument to specify the panel unit identifier required when `time_transform = TRUE`.
  - This enables seamless analysis of panel data with monthly, quarterly, or irregular time formats.

## Improvements
- Updated input validation:
  - Now accepts `Date` class in the `time` variable and converts it automatically to numeric if `time_transform = FALSE`.
  - Improved error messages and warnings for better user feedback when `unit` is missing or `time` is of unsupported type.
- Enhanced documentation:
  - Updated `@examples` in the function documentation to include `Date`-based examples.
  - Extended vignette to illustrate `time_transform` usage.
  - Expanded `README.md` to describe irregular time handling and demonstrate new use cases.
- Strengthened test coverage:
  - Added new unit tests for `time_transform`, `unit` handling, and `Date` conversion edge cases.

## Compatibility
- No breaking changes.
- This is a backward-compatible **minor release** introducing significant new functionality for irregular time formats.

---

# fixes 0.2.1 (May 11, 2025)

## Minor Improvements
- Added a warning when lead/lag dummy variable names (e.g., `lead1`, `lag0`) already exist in the dataset to prevent accidental overwriting.
- Added a warning when filtered data (based on `lead_range`, `lag_range`, and `interval`) has fewer than 10 rows, helping users identify overly narrow estimation windows.
- Improved handling of the `treatment` variable: it is now coerced to logical using `as.logical()` to support both binary numeric (`0/1`) and logical (`TRUE/FALSE`) formats.
- Fixed internal bug in model formula construction:
  - Previously, fixed effects specified via the `fe` argument (e.g., `~ id + year`) were combined using `model_formula | fe_text`, which caused evaluation errors during tests.
  - Now, the full model formula is safely constructed as a string and parsed with `as.formula()` to ensure compatibility with `fixest::feols()`.

## Compatibility
- No breaking changes. This is a backward-compatible patch release with internal robustness improvements and enhanced error handling.

# fixes 0.2.0 (March 29, 2025)

## Major Features
- **Support for covariates in `run_es()`**:
  - Covariates must now be specified as a **one-sided formula** (e.g., `~ x1 + x2`).
- **Fixed Effects and Clustering Interface Updated**:
  - `fe` and `cluster` arguments must now be specified using a **one-sided formula** (e.g., `~ id + year`).
  - Character vector input for `cluster` is still accepted.
  - Improved internal handling and validation of fixed effects and clustering variables.
- Improved error messages for invalid or missing variable names.

# fixes 0.1.0 (March 17, 2025)

## Major Changes
- `fe_var` argument now supports additive notation (`firm_id + year`) instead of character vectors.
- Improved `plot_es()` efficiency and documentation.

## Minor Improvements
- Fixed cluster variable handling to correctly reference column names.
- Updated test cases to support new `fe` notation.
- Improved package documentation.

# fixes 0.0.2 (Enhancements & Fixes)

This version introduced several enhancements and refinements to improve usability and maintainability.

## Improvements
- Refactored variable name handling:
  - `outcome_var`, `treated_var`, and `time_var` are now processed using `rlang::ensym()` for better robustness.
  - `fe_var` and `cluster_var` handling improved for more reliable column referencing.
- More informative **error messages** when variables are missing in the dataset.
- Enhanced **baseline term handling** in regression models to prevent incorrect factor levels.
- Improved `plot_es()` function:
  - Added validation checks to ensure required columns (`relative_time`, `estimate`, etc.) are present.
  - Adjusted confidence interval calculations to avoid missing values in error bars.

## Fixes
- Addressed an issue where `baseline` handling could lead to incorrect sorting of lead/lag terms.
- Resolved a minor inconsistency in fixed effects variable name parsing.

# fixes 0.0.1 (Initial Release)

This is the first release of the `fixes` package, providing tools for estimating and visualizing event study models with fixed effects.

## Features
- **`run_es()`**: A function to estimate event study models using `fixest::feols()`, generating lead and lag variables automatically.
  - Supports **fixed effects** (`fe_var` as character vector).
  - Allows **clustered standard errors** via `cluster_var`.
  - Handles **time scaling** through the `interval` argument.
- **`plot_es()`**: A function to visualize event study results with ggplot2.
  - Supports **ribbon-style confidence intervals** (`type = "ribbon"`, default).
  - Allows **error bar visualization** (`type = "errorbar"`).
  - Customizable plot elements including colors, line styles, and reference lines.

## Initial Implementation
- **Fixed effects regression model** using `fixest::feols()`.
- **Automated creation of lead/lag dummy variables** based on treatment timing.
- **Baseline period exclusion** to avoid multicollinearity.
- **Support for custom time intervals (`interval` argument)**.

## Limitations in 0.0.1
- Fixed effects must be specified as a **character vector (`c("firm_id", "year")`)**.
- Clustered standard errors require variable names as **character strings (`"state_id"`)**.
- No direct support for additive notation in `fe_var`.
